This commit is contained in:
Yuzu 2024-12-07 18:01:01 -05:00
parent f1f723d531
commit 0412d6b404
4 changed files with 271 additions and 14 deletions

View File

@ -71,3 +71,27 @@ Contributions are welcome! Please open an issue or pull request if you'd like to
| voice_channel_effect_send | ❌ | | voice_channel_effect_send | ❌ |
| voice_state_update | ❌ | | voice_state_update | ❌ |
| voice_server_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 | ❌ |

View File

@ -40,14 +40,19 @@ pub const FetchReq = struct {
token: []const u8, token: []const u8,
client: http.Client, client: http.Client,
body: std.ArrayList(u8), 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 { pub fn init(allocator: mem.Allocator, token: []const u8) FetchReq {
const client = http.Client{ .allocator = allocator }; const client = http.Client{ .allocator = allocator };
return FetchReq{ return FetchReq{
.allocator = allocator, .allocator = allocator,
.client = client, .client = client,
.body = std.ArrayList(u8).init(allocator),
.token = token, .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(); 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) { pub fn get(self: *FetchReq, comptime T: type, path: []const u8) !zjson.Owned(T) {
const result = try self.makeRequest(.GET, path, null); const result = try self.makeRequest(.GET, path, null);
if (result.status != .ok) if (result.status != .ok)
@ -207,16 +242,16 @@ pub const FetchReq = struct {
to_post: ?[]const u8, to_post: ?[]const u8,
) MakeRequestError!http.Client.FetchResult { ) MakeRequestError!http.Client.FetchResult {
var buf: [256]u8 = undefined; 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{ var fetch_options = http.Client.FetchOptions{
.location = http.Client.FetchOptions.Location{ .url = constructed }, .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, .method = method,
.extra_headers = try self.extra_headers.toOwnedSlice(),
.response_storage = .{ .dynamic = &self.body }, .response_storage = .{ .dynamic = &self.body },
}; };
@ -271,13 +306,14 @@ pub const FetchReq = struct {
}; };
var uri_buf: [256]u8 = undefined; 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 server_header_buffer: [16 * 1024]u8 = undefined;
var request = try self.client.open(method, uri, .{ var request = try self.client.open(method, uri, .{
.keep_alive = false, .keep_alive = false,
.server_header_buffer = &server_header_buffer, .server_header_buffer = &server_header_buffer,
.headers = headers, .headers = headers,
.extra_headers = try self.extra_headers.toOwnedSlice(),
}); });
defer request.deinit(); defer request.deinit();
request.transfer_encoding = .{ .content_length = body.len }; request.transfer_encoding = .{ .content_length = body.len };

View File

@ -200,7 +200,6 @@ inline fn _connect_ws(allocator: mem.Allocator, url: []const u8) !ws.Client {
.host = url, .host = url,
}); });
// maybe change this to a buffer
var buf: [0x100]u8 = undefined; var buf: [0x100]u8 = undefined;
const host = try std.fmt.bufPrint(&buf, "host: {s}", .{url}); 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. /// Create a new DM channel with a user.
/// Returns a DM channel object (if one already exists, it will be returned instead). /// 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); var req = FetchReq.init(self.allocator, self.details.token);
defer req.deinit(); defer req.deinit();
const dm = try req.post(Types.Channel, "/users/@me/channels", .{ .recipient_id = whom }); return req.post(Types.Channel, "/users/@me/channels", .{ .recipient_id = whom });
return dm;
} }
/// Create a new group DM channel with multiple users. /// Create a new group DM channel with multiple users.
@ -2241,3 +2239,202 @@ pub fn updateMyApplicationConnection(self: *Self) RequestFailedError!void {
_ = self; _ = self;
@panic("unimplemented\n"); @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;
}

View File

@ -52,8 +52,8 @@ pub const Snowflake = enum(u64) {
unreachable; 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; 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()}));
} }
}; };