From 0412d6b404300352e2208512f06413262c8b228b Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sat, 7 Dec 2024 18:01:01 -0500 Subject: [PATCH] a --- README.md | 24 ++++ src/http.zig | 52 +++++++-- src/shard.zig | 205 ++++++++++++++++++++++++++++++++++- src/structures/snowflake.zig | 4 +- 4 files changed, 271 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index fce0dec..c491284 100644 --- a/README.md +++ b/README.md @@ -71,3 +71,27 @@ Contributions are welcome! Please open an issue or pull request if you'd like to | voice_channel_effect_send | ❌ | | voice_state_update | ❌ | | voice_server_update | ❌ | + +## http methods missing +| Endpoint | Support | +|----------------------------------------|---------| +| Application related | ❌ | +| Audit log | ❌ | +| Automod | ❌ | +| Channel related | ✅ | +| Emoji related | ✅ | +| Entitlement related | ❌ | +| Guild related | ✅ | +| Guild Scheduled Event related | ❌ | +| Guild template related | ❌ | +| Invite related | ✅ | +| Message related | ✅ | +| Poll related | ✅ | +| SKU related | ❌ | +| Soundboard related | ❌ | +| Stage Instance related | ❌ | +| Sticker related | ❌ | +| Subscription related | ❌ | +| User related | ✅ | +| Voice related | ❌ | +| Webhook related | ❌ | diff --git a/src/http.zig b/src/http.zig index cf469b6..bb2fe17 100644 --- a/src/http.zig +++ b/src/http.zig @@ -40,14 +40,19 @@ pub const FetchReq = struct { token: []const u8, client: http.Client, body: std.ArrayList(u8), + /// internal + extra_headers: std.ArrayList(http.Header), + query_params: std.StringArrayHashMap([]const u8), pub fn init(allocator: mem.Allocator, token: []const u8) FetchReq { const client = http.Client{ .allocator = allocator }; return FetchReq{ .allocator = allocator, .client = client, - .body = std.ArrayList(u8).init(allocator), .token = token, + .body = std.ArrayList(u8).init(allocator), + .extra_headers = std.ArrayList(http.Header).init(allocator), + .query_params = std.StringArrayHashMap([]const u8).init(allocator), }; } @@ -56,6 +61,36 @@ pub const FetchReq = struct { self.body.deinit(); } + pub fn addHeader(self: *FetchReq, name: []const u8, value: []const u8) !void { + try self.extra_headers.append(http.Header{ .name = name, .value = value }); + } + + pub fn addQueryParam(self: *FetchReq, name: []const u8, value: []const u8) !void { + try self.query_params.put(name, value); + } + + fn formatQueryParams(self: *FetchReq) ![]const u8 { + var query = std.ArrayListUnmanaged(u8){}; + const writer = query.writer(self.allocator); + + if (self.query_params.count() == 0) + return ""; + + _ = try writer.write("?"); + var it = self.query_params.iterator(); + while (it.next()) |kv| { + _ = try writer.write(kv.key_ptr.*); + _ = try writer.write("="); + _ = try writer.write(kv.value_ptr.*); + if (it.next()) |_| { + try writer.writeByte('&'); + continue; + } + } + + return query.toOwnedSlice(self.allocator); + } + pub fn get(self: *FetchReq, comptime T: type, path: []const u8) !zjson.Owned(T) { const result = try self.makeRequest(.GET, path, null); if (result.status != .ok) @@ -207,16 +242,16 @@ pub const FetchReq = struct { 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 }); + const constructed = try std.fmt.bufPrint(&buf, "{s}{s}{s}", .{ BASE_URL, path, try self.formatQueryParams() }); + + try self.extra_headers.append(http.Header{ .name = "Accept", .value = "application/json" }); + try self.extra_headers.append(http.Header{ .name = "Content-Type", .value = "application/json" }); + try self.extra_headers.append(http.Header{ .name = "Authorization", .value = self.token }); var fetch_options = http.Client.FetchOptions{ .location = http.Client.FetchOptions.Location{ .url = constructed }, - .extra_headers = &[_]http.Header{ - http.Header{ .name = "Accept", .value = "application/json" }, - http.Header{ .name = "Content-Type", .value = "application/json" }, - http.Header{ .name = "Authorization", .value = self.token }, - }, .method = method, + .extra_headers = try self.extra_headers.toOwnedSlice(), .response_storage = .{ .dynamic = &self.body }, }; @@ -271,13 +306,14 @@ pub const FetchReq = struct { }; var uri_buf: [256]u8 = undefined; - const uri = try std.Uri.parse(try std.fmt.bufPrint(&uri_buf, "{s}{s}", .{ BASE_URL, path })); + const uri = try std.Uri.parse(try std.fmt.bufPrint(&uri_buf, "{s}{s}{s}", .{ BASE_URL, path, try self.formatQueryParams() })); 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, + .extra_headers = try self.extra_headers.toOwnedSlice(), }); defer request.deinit(); request.transfer_encoding = .{ .content_length = body.len }; diff --git a/src/shard.zig b/src/shard.zig index 3c4d3a8..3473899 100644 --- a/src/shard.zig +++ b/src/shard.zig @@ -200,7 +200,6 @@ inline fn _connect_ws(allocator: mem.Allocator, url: []const u8) !ws.Client { .host = url, }); - // maybe change this to a buffer var buf: [0x100]u8 = undefined; const host = try std.fmt.bufPrint(&buf, "host: {s}", .{url}); @@ -2204,12 +2203,11 @@ pub fn leaveGuild(self: *Self, guild_id: Snowflake) RequestFailedError!void { /// Create a new DM channel with a user. /// Returns a DM channel object (if one already exists, it will be returned instead). -pub fn Dm(self: *Self, whom: Snowflake) RequestFailedError!zjson.Owned(Types.Channel) { +pub fn dm(self: *Self, whom: Snowflake) RequestFailedError!zjson.Owned(Types.Channel) { var req = FetchReq.init(self.allocator, self.details.token); defer req.deinit(); - const dm = try req.post(Types.Channel, "/users/@me/channels", .{ .recipient_id = whom }); - return dm; + return req.post(Types.Channel, "/users/@me/channels", .{ .recipient_id = whom }); } /// Create a new group DM channel with multiple users. @@ -2241,3 +2239,202 @@ pub fn updateMyApplicationConnection(self: *Self) RequestFailedError!void { _ = self; @panic("unimplemented\n"); } + +// start methods for emojis + +/// Returns a list of emoji objects for the given guild. +/// Includes `user` fields if the bot has the `CREATE_GUILD_EXPRESSIONS` or `MANAGE_GUILD_EXPRESSIONS` permission. +pub fn fetchEmojis(self: *Self, guild_id: Snowflake) RequestFailedError!zjson.Owned([]Types.Emoji) { + var buf: [256]u8 = undefined; + const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/emojis", .{guild_id.into()}); + + var req = FetchReq.init(self.allocator, self.details.token); + defer req.deinit(); + + const emojis = try req.get([]Types.Emoji, path); + return emojis; +} + +/// Returns an emoji object for the given guild and emoji IDs. +/// Includes the `user` field if the bot has the `MANAGE_GUILD_EXPRESSIONS` permission, or if the bot created the emoji and has the the `CREATE_GUILD_EXPRESSIONS` permission. +pub fn fetchEmoji(self: *Self, guild_id: Snowflake, emoji_id: Snowflake) RequestFailedError!zjson.Owned(Types.Emoji) { + var buf: [256]u8 = undefined; + const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/emojis/{d}", .{ guild_id.into(), emoji_id.into() }); + + var req = FetchReq.init(self.allocator, self.details.token); + defer req.deinit(); + + const emoji = try req.get(Types.Emoji, path); + return emoji; +} + +/// Create a new emoji for the guild. +/// Requires the `CREATE_GUILD_EXPRESSIONS` permission. +/// Returns the new emoji object on success. +/// Fires a Guild Emojis Update Gateway event. +pub fn createEmoji(self: *Self, guild_id: Snowflake, emoji: Types.CreateGuildEmoji) RequestFailedError!zjson.Owned(Types.Emoji) { + var buf: [256]u8 = undefined; + const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/emojis", .{guild_id.into()}); + + var req = FetchReq.init(self.allocator, self.details.token); + defer req.deinit(); + + return req.post(Types.Emoji, path, emoji); +} + +/// Modify the given emoji. +/// For emojis created by the current user, requires either the `CREATE_GUILD_EXPRESSIONS` or `MANAGE_GUILD_EXPRESSIONS` permission. +/// For other emojis, requires the `MANAGE_GUILD_EXPRESSIONS` permission. +/// Returns the updated emoji object on success. +/// Fires a Guild Emojis Update Gateway event. +pub fn editEmoji(self: *Self, guild_id: Snowflake, emoji: Types.ModifyGuildEmoji) RequestFailedError!zjson.Owned(Types.Emoji) { + var buf: [256]u8 = undefined; + const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/emojis", .{guild_id.into()}); + + var req = FetchReq.init(self.allocator, self.details.token); + defer req.deinit(); + + return req.patch(Types.Emoji, path, emoji); +} + +/// Delete the given emoji. +/// For emojis created by the current user, requires either the `CREATE_GUILD_EXPRESSIONS` or `MANAGE_GUILD_EXPRESSIONS` permission. +/// For other emojis, requires the `MANAGE_GUILD_EXPRESSIONS` permission. +/// Returns 204 No Content on success. +/// Fires a Guild Emojis Update Gateway event. +pub fn deleteEmoji(self: *Self, guild_id: Snowflake, emoji_id: Snowflake) RequestFailedError!zjson.Owned(Types.Emoji) { + var buf: [256]u8 = undefined; + const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/emojis/{d}", .{ guild_id.into(), emoji_id.into() }); + + var req = FetchReq.init(self.allocator, self.details.token); + defer req.deinit(); + + try req.delete(path); +} + +/// Returns an object containing a list of emoji objects for the given application under the `items` key. +/// Includes a `user` object for the team member that uploaded the emoji from the app's settings, or for the bot user if uploaded using the API. +pub fn fetchApplicationEmojis(self: *Self, application_id: Snowflake) RequestFailedError!zjson.Owned([]Types.Emoji) { + var buf: [256]u8 = undefined; + const path = try std.fmt.bufPrint(&buf, "/applications/{d}/emojis", .{application_id.into()}); + + var req = FetchReq.init(self.allocator, self.details.token); + defer req.deinit(); + + const emojis = try req.get([]Types.Emoji, path); + return emojis; +} + +/// Returns an emoji object for the given application and emoji IDs. Includes the user field. +pub fn fetchApplicationEmoji(self: *Self, application_id: Snowflake, emoji_id: Snowflake) RequestFailedError!zjson.Owned(Types.Emoji) { + var buf: [256]u8 = undefined; + const path = try std.fmt.bufPrint(&buf, "/applications/{d}/emojis/{d}", .{ application_id.into(), emoji_id.into() }); + + var req = FetchReq.init(self.allocator, self.details.token); + defer req.deinit(); + + const emoji = try req.get(Types.Emoji, path); + return emoji; +} + +/// Create a new emoji for the application. Returns the new emoji object on success. +pub fn createApplicationEmoji(self: *Self, application_id: Snowflake, emoji: Types.CreateGuildEmoji) RequestFailedError!zjson.Owned(Types.Emoji) { + var buf: [256]u8 = undefined; + const path = try std.fmt.bufPrint(&buf, "/applications/{d}/emojis", .{application_id.into()}); + + var req = FetchReq.init(self.allocator, self.details.token); + defer req.deinit(); + + return req.post(Types.Emoji, path, emoji); +} + +/// Modify the given emoji. Returns the updated emoji object on success. +pub fn editApplicationEmoji(self: *Self, application_id: Snowflake, emoji: Types.ModifyGuildEmoji) RequestFailedError!zjson.Owned(Types.Emoji) { + var buf: [256]u8 = undefined; + const path = try std.fmt.bufPrint(&buf, "/applications/{d}/emojis", .{application_id.into()}); + + var req = FetchReq.init(self.allocator, self.details.token); + defer req.deinit(); + + return req.patch(Types.Emoji, path, emoji); +} + +/// Delete the given emoji. Returns 204 No Content on success. +pub fn deleteApplicationEmoji(self: *Self, application_id: Snowflake, emoji_id: Snowflake) RequestFailedError!zjson.Owned(Types.Emoji) { + var buf: [256]u8 = undefined; + const path = try std.fmt.bufPrint(&buf, "/applications/{d}/emojis/{d}", .{ application_id.into(), emoji_id.into() }); + + var req = FetchReq.init(self.allocator, self.details.token); + defer req.deinit(); + + try req.delete(path); +} + +// start invites + +/// Returns an invite object for the given code. +pub fn fetchInvite(self: *Self, code: []const u8) RequestFailedError!zjson.Owned(Types.Invite) { + var buf: [256]u8 = undefined; + const path = try std.fmt.bufPrint(&buf, "/invites/{s}", .{code}); + + var req = FetchReq.init(self.allocator, self.details.token); + defer req.deinit(); + + return req.get(Types.Invite, path); +} + +/// Delete an invite. +/// Requires the `MANAGE_CHANNELS` permission on the channel this invite belongs to, or `MANAGE_GUILD` to remove any invite across the guild. +/// Returns an invite object on success. +/// Fires an Invite Delete Gateway event. +pub fn deleteInvite(self: *Self, code: []const u8) RequestFailedError!void { + var buf: [256]u8 = undefined; + const path = try std.fmt.bufPrint(&buf, "/invites/{s}", .{code}); + + var req = FetchReq.init(self.allocator, self.details.token); + defer req.deinit(); + + try req.delete(path); +} + +// poll stuff + +/// Get a list of users that voted for this specific answer. +pub fn fetchAnswerVoters( + self: *Self, + channel_id: Snowflake, + poll_id: Snowflake, + answer_id: Snowflake, +) RequestFailedError!zjson.Owner([]Types.User) { + var buf: [256]u8 = undefined; + const path = try std.fmt.bufPrint(&buf, "/channels/{d}/polls/{d}/answers/{d}", .{ + channel_id.into(), + poll_id.into(), + answer_id.into(), + }); + + var req = FetchReq.init(self.allocator, self.details.token); + defer req.deinit(); + + const voters = try req.get([]Types.User, path); + return voters; +} + +/// Immediately ends the poll. +/// You cannot end polls from other users. +/// +/// Returns a message object. Fires a Message Update Gateway event. +pub fn endPoll( + self: *Self, + channel_id: Snowflake, + poll_id: Snowflake, +) RequestFailedError!zjson.Owned(Types.Message) { + var buf: [256]u8 = undefined; + const path = try std.fmt.bufPrint(&buf, "/channels/{d}/polls/{d}/expire", .{ channel_id.into(), poll_id.into() }); + + var req = FetchReq.init(self.allocator, self.details.token); + defer req.deinit(); + + const msg = try req.post(Types.Message, path); + return msg; +} diff --git a/src/structures/snowflake.zig b/src/structures/snowflake.zig index 8ad00ba..d8cfc08 100644 --- a/src/structures/snowflake.zig +++ b/src/structures/snowflake.zig @@ -52,8 +52,8 @@ pub const Snowflake = enum(u64) { unreachable; } - pub fn format(self: Snowflake) ![]const u8 { + pub fn format(self: Snowflake, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { var buf: [256]u8 = undefined; - return std.fmt.bufPrint(&buf, "{d}\n", .{self.into()}); + try writer.writeAll(try std.fmt.bufPrint(&buf, "{d}\n", .{self.into()})); } };