add X-Audit-Log-Reason header

This commit is contained in:
Yuzu 2024-12-10 12:55:59 -05:00
parent 087686b0d5
commit 519dd143b4
10 changed files with 328 additions and 96 deletions

View File

@ -9,12 +9,6 @@ pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast });
// this is your own program
const dzig = b.addModule("discord.zig", .{
.root_source_file = b.path("src/discord.zig"),
.link_libc = true,
});
const websocket = b.dependency("websocket", .{
.target = target,
.optimize = optimize,
@ -27,6 +21,15 @@ pub fn build(b: *std.Build) void {
.optimize = optimize,
});
const dzig = b.addModule("discord.zig", .{
.root_source_file = b.path("src/discord.zig"),
.link_libc = true,
});
dzig.addImport("ws", websocket.module("websocket"));
dzig.addImport("zlib", zlib.module("zlib"));
dzig.addImport("deque", deque.module("zig-deque"));
const marin = b.addExecutable(.{
.name = "marin",
.root_source_file = b.path("src/test.zig"),
@ -35,12 +38,6 @@ pub fn build(b: *std.Build) void {
.link_libc = true,
});
// now install your own executable after it's built correctly
dzig.addImport("ws", websocket.module("websocket"));
dzig.addImport("zlib", zlib.module("zlib"));
dzig.addImport("deque", deque.module("zig-deque"));
marin.root_module.addImport("discord.zig", dzig);
marin.root_module.addImport("ws", websocket.module("websocket"));
marin.root_module.addImport("zlib", zlib.module("zlib"));
@ -54,4 +51,25 @@ pub fn build(b: *std.Build) void {
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
const lib = b.addStaticLibrary(.{
.name = "discord.zig",
.root_source_file = b.path("src/discord.zig"),
.target = target,
.optimize = optimize,
});
lib.root_module.addImport("ws", websocket.module("websocket"));
lib.root_module.addImport("zlib", zlib.module("zlib"));
lib.root_module.addImport("deque", deque.module("zig-deque"));
// docs
const docs_step = b.step("docs", "Generate documentation");
const docs_install = b.addInstallDirectory(.{
.source_dir = lib.getEmittedDocs(),
.install_dir = .prefix,
.install_subdir = "docs",
});
docs_step.dependOn(&docs_install.step);
}

View File

@ -27,18 +27,11 @@ const std = @import("std");
const mem = std.mem;
const debug = @import("internal.zig").debug;
pub const discord_epoch = 1420070400000;
/// Calculate and return the shard ID for a given guild ID
pub inline fn calculateShardId(guild_id: Snowflake, shards: ?usize) u64 {
return (guild_id.into() >> 22) % shards orelse 1;
}
/// Convert a timestamp to a snowflake.
pub inline fn snowflakeToTimestamp(id: Snowflake) u64 {
return (id.into() >> 22) + discord_epoch;
}
const Self = @This();
shard_details: ShardDetails,

View File

@ -61,8 +61,9 @@ 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 addHeader(self: *FetchReq, name: []const u8, value: ?[]const u8) !void {
if (value) |some|
try self.extra_headers.append(http.Header{ .name = name, .value = some });
}
pub fn addQueryParam(self: *FetchReq, name: []const u8, value: anytype) !void {

View File

@ -941,8 +941,6 @@ pub fn repetition(comptime T: type, parser: Parser(T)) Parser([]T) {
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)) {
.void => return {},
@ -950,15 +948,15 @@ pub fn parseInto(comptime T: type, allocator: mem.Allocator, value: JsonType) Er
return value.bool;
},
.int, .comptime_int => {
// std.debug.assert(value.number.is(.integer));
std.debug.assert(value.number.is(.integer)); // attempting to cast an int against a non-int
return value.number.cast(T);
},
.float, .comptime_float => {
// std.debug.assert(value.number.is(.float));
std.debug.assert(value.number.is(.float)); // attempting to cast a float against a non-float
return value.number.cast(T);
},
.null => {
// std.debug.assert(value.is(.null));
std.debug.assert(value.is(.null)); // nullable or required property marked explicitly as null
return null;
},
.optional => |optionalInfo| {
@ -969,13 +967,38 @@ pub fn parseInto(comptime T: type, allocator: mem.Allocator, value: JsonType) Er
if (std.meta.hasFn(T, "toJson")) {
return try T.toJson(allocator, value);
}
if (unionInfo.tag_type == null)
@compileError("Unable to parse into untagged union '" ++ @typeName(T) ++ "'");
var result: ?T = null;
if (unionInfo.tag_type == null) {
switch (value) {
.string => |string| inline for (unionInfo.fields) |u_field| {
if (u_field.type == []const u8)
result = @unionInit(T, u_field.name, string);
},
.bool => |bool_| inline for (unionInfo.fields) |u_field| {
if (u_field.type == bool)
result = @unionInit(T, u_field.name, bool_);
},
.number => |number| inline for (unionInfo.fields) |u_field| {
switch (number) {
.integer => |i| if (u_field.type == @TypeOf(i)) {
result = @unionInit(T, u_field.name, i);
},
.float => |f| if (u_field.type == @TypeOf(f)) {
result = @unionInit(T, u_field.name, f);
},
}
},
else => return error.TypeMismatch, // may only cast string, bool, number
}
return result.?;
}
const fieldname = switch (value) {
.string => |slice| slice,
else => @panic("can only cast strings"),
else => @panic("can only cast strings for untagged union"),
};
inline for (unionInfo.fields) |u_field| {
@ -983,7 +1006,7 @@ pub fn parseInto(comptime T: type, allocator: mem.Allocator, value: JsonType) Er
if (u_field.type == void) {
result = @unionInit(T, u_field.name, {});
} else {
@panic("unions may only contain empty values");
@panic("tagged unions may only contain empty values");
}
}
}
@ -1027,8 +1050,7 @@ pub fn parseInto(comptime T: type, allocator: mem.Allocator, value: JsonType) Er
.array => |arrayInfo| {
switch (value) {
.string => |string| {
if (arrayInfo.child != u8)
return error.TypeMismatch;
if (arrayInfo.child != u8) return error.TypeMismatch; // attempting to cast an array of T against a string
var r: T = undefined;
var i: usize = 0;
while (i < arrayInfo.len) : (i += 1)
@ -1047,8 +1069,7 @@ pub fn parseInto(comptime T: type, allocator: mem.Allocator, value: JsonType) Er
},
.pointer => |ptrInfo| switch (ptrInfo.size) {
.One => {
// we simply allocate the type and return an address instead
// of just returning the type
// 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;
@ -1056,8 +1077,8 @@ pub fn parseInto(comptime T: type, allocator: mem.Allocator, value: JsonType) Er
.Slice => switch (value) {
.array => |array| {
var arraylist: std.ArrayList(ptrInfo.child) = .init(allocator);
try arraylist.ensureUnusedCapacity(array.len);
for (array) |jsonval| {
try arraylist.ensureUnusedCapacity(1);
const item = try parseInto(ptrInfo.child, allocator, jsonval);
arraylist.appendAssumeCapacity(item);
}
@ -1070,14 +1091,16 @@ pub fn parseInto(comptime T: type, allocator: mem.Allocator, value: JsonType) Er
.string => |string| {
if (ptrInfo.child == u8) {
var arraylist: std.ArrayList(u8) = .init(allocator);
for (string) |char| {
try arraylist.ensureUnusedCapacity(1);
try arraylist.ensureUnusedCapacity(string.len);
for (string) |char|
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;
@ -1089,11 +1112,12 @@ pub fn parseInto(comptime T: type, allocator: mem.Allocator, value: JsonType) Er
return try arraylist.toOwnedSlice();
}
},
else => return error.TypeMismatch,
else => return error.TypeMismatch, // may only cast string, array
},
else => {
if (std.meta.hasFn(T, "toJson"))
return T.toJson(allocator, value);
return error.TypeMismatch; // unsupported type
},
},
else => @compileError("Unable to parse into type '" ++ @typeName(T) ++ "'"),

View File

@ -1508,13 +1508,15 @@ pub fn createChannel(self: *Self, guild_id: Snowflake, create_channel: Types.Cre
/// Method to fetch a guild
/// Returns the guild object for the given id.
/// If `with_counts` is set to true, this endpoint will also return `approximate_member_count` and `approximate_presence_count` for the guild.
pub fn fetchGuild(self: *Self, guild_id: Snowflake) RequestFailedError!zjson.Owned(Types.Guild) {
pub fn fetchGuild(self: *Self, guild_id: Snowflake, with_counts: ?bool) RequestFailedError!zjson.Owned(Types.Guild) {
var buf: [256]u8 = undefined;
const path = try std.fmt.bufPrint(&buf, "/guilds/{d}", .{guild_id.into()});
var req = FetchReq.init(self.allocator, self.details.token);
defer req.deinit();
try req.addQueryParam("with_counts", with_counts);
const res = try req.get(Types.Guild, path);
return res;
}
@ -1582,7 +1584,6 @@ pub fn editGuildChannelPositions(self: *Self, guild_id: Snowflake, edit_guild_ch
/// Method to get a guild's active threads
/// Returns all active threads in the guild, including public and private threads.
/// Threads are ordered by their `id`, in descending order.
/// TODO: implement query string parameters
pub fn fetchGuildActiveThreads(self: *Self, guild_id: Snowflake) RequestFailedError!zjson.Owned(Types.Channel) {
var buf: [256]u8 = undefined;
const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/threads/active", .{guild_id.into()});
@ -1607,36 +1608,50 @@ pub fn fetchMember(self: *Self, guild_id: Snowflake, user_id: Snowflake) Request
return res;
}
pub const ListGuildMembersQuery = struct {
/// max number of members to return (1-1000)
limit: u16 = 1,
/// the highest user id in the previous page
after: Snowflake = Snowflake.from(0),
};
/// Method to get the members of a guild
/// Returns a list of guild member objects that are members of the guild.
/// TODO: implement query string parameters
pub fn fetchMembers(self: *Self, guild_id: Snowflake) RequestFailedError!zjson.Owned([]Types.Member) {
pub fn fetchMembers(self: *Self, guild_id: Snowflake, query: ListGuildMembersQuery) RequestFailedError!zjson.Owned([]Types.Member) {
var buf: [256]u8 = undefined;
const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/members", .{guild_id.into()});
var req = FetchReq.init(self.allocator, self.details.token);
defer req.deinit();
try req.addQueryParam("limit", query.limit);
try req.addQueryParam("after", query.after);
const res = try req.get([]Types.Member, path);
return res;
}
pub const SearchGuildMembersQuery = struct {
/// Query string to match username(s) and nickname(s) against
query: []const u8,
/// max number of members to return (1-1000)
limit: u16,
};
/// Method to find members
/// Returns a list of guild member objects whose username or nickname starts with a provided string.
pub fn searchMembers(self: *Self, guild_id: Snowflake, query: struct {
query: []const u8,
limit: usize,
}) RequestFailedError!zjson.Owned([]Types.Member) {
pub fn searchMembers(self: *Self, guild_id: Snowflake, query: SearchGuildMembersQuery) RequestFailedError!zjson.Owned([]Types.Member) {
var buf: [256]u8 = undefined;
const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/members/search?query={s}&limit={d}", .{
const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/members/search", .{
guild_id.into(),
query.query,
query.limit,
});
var req = FetchReq.init(self.allocator, self.details.token);
defer req.deinit();
try req.addQueryParam("query", query.query);
try req.addQueryParam("limit", query.limit);
const res = try req.get([]Types.Member, path);
return res;
}
@ -1663,49 +1678,63 @@ pub fn addMember(self: *Self, guild_id: Snowflake, user_id: Snowflake, credentia
/// Returns a 200 OK with the guild member as the body.
/// Fires a Guild Member Update Gateway event. If the channel_id is set to null,
/// this will force the target user to be disconnected from voice.
pub fn editMember(self: *Self, guild_id: Snowflake, user_id: Snowflake, attributes: Types.ModifyGuildMember) RequestFailedError!?zjson.Owned(Types.Member) {
pub fn editMember(
self: *Self,
guild_id: Snowflake,
user_id: Snowflake,
attributes: Types.ModifyGuildMember,
reason: ?[]const u8,
) RequestFailedError!?zjson.Owned(Types.Member) {
var buf: [256]u8 = undefined;
const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/members/{d}", .{ guild_id.into(), user_id.into() });
var req = FetchReq.init(self.allocator, self.details.token);
defer req.deinit();
try req.addHeader("X-Audit-Log-Reason", reason);
const res = try req.patch(Types.Member, path, attributes);
return res;
}
pub fn editCurrentMember(self: *Self, guild_id: Snowflake, attributes: Types.ModifyGuildMember) RequestFailedError!?zjson.Owned(Types.Member) {
pub fn editCurrentMember(self: *Self, guild_id: Snowflake, attributes: Types.ModifyGuildMember, reason: ?[]const u8) RequestFailedError!?zjson.Owned(Types.Member) {
var buf: [256]u8 = undefined;
const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/members/@me", .{guild_id.into()});
var req = FetchReq.init(self.allocator, self.details.token);
defer req.deinit();
try req.addHeader("X-Audit-Log-Reason", reason);
const res = try req.patch(Types.Member, path, attributes);
return res;
}
/// change's someones's nickname
pub fn changeNickname(self: *Self, guild_id: Snowflake, user_id: Snowflake, attributes: Types.ModifyGuildMember) RequestFailedError!void {
pub fn changeNickname(self: *Self, guild_id: Snowflake, user_id: Snowflake, nick: []const u8, reason: ?[]const u8) RequestFailedError!void {
var buf: [256]u8 = undefined;
const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/members/{d}", .{ guild_id.into(), user_id.into() });
var req = FetchReq.init(self.allocator, self.details.token);
defer req.deinit();
const res = try req.patch(Types.Member, path, attributes);
try req.addHeader("X-Audit-Log-Reason", reason);
const res = try req.patch(Types.Member, path, .{ .nick = nick });
defer res.deinit();
}
/// change's someones's nickname
pub fn changeMyNickname(self: *Self, guild_id: Snowflake, attributes: Types.ModifyGuildMember) RequestFailedError!void {
pub fn changeMyNickname(self: *Self, guild_id: Snowflake, nick: []const u8, reason: ?[]const u8) RequestFailedError!void {
var buf: [256]u8 = undefined;
const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/members/@me", .{guild_id.into()});
var req = FetchReq.init(self.allocator, self.details.token);
defer req.deinit();
const res = try req.patch(Types.Member, path, attributes);
try req.addHeader("X-Audit-Log-Reason", reason);
const res = try req.patch(Types.Member, path, .{ .nick = nick });
defer res.deinit();
}
@ -1717,6 +1746,7 @@ pub fn addRole(
guild_id: Snowflake,
user_id: Snowflake,
role_id: Snowflake,
reason: ?[]const u8,
) RequestFailedError!void {
var buf: [256]u8 = undefined;
const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/members/{d}/roles/{d}", .{
@ -1728,6 +1758,8 @@ pub fn addRole(
var req = FetchReq.init(self.allocator, self.details.token);
defer req.deinit();
try req.addHeader("X-Audit-Log-Reason", reason);
try req.put3(path);
}
@ -1740,6 +1772,7 @@ pub fn removeRole(
guild_id: Snowflake,
user_id: Snowflake,
role_id: Snowflake,
reason: ?[]const u8,
) RequestFailedError!void {
var buf: [256]u8 = undefined;
const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/members/{d}/roles/{d}", .{
@ -1751,6 +1784,8 @@ pub fn removeRole(
var req = FetchReq.init(self.allocator, self.details.token);
defer req.deinit();
try req.addHeader("X-Audit-Log-Reason", reason);
try req.delete(path);
}
@ -1762,6 +1797,7 @@ pub fn kickMember(
self: *Self,
guild_id: Snowflake,
user_id: Snowflake,
reason: ?[]const u8,
) RequestFailedError!void {
var buf: [256]u8 = undefined;
const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/members/{d}", .{ guild_id.into(), user_id.into() });
@ -1769,9 +1805,20 @@ pub fn kickMember(
var req = FetchReq.init(self.allocator, self.details.token);
defer req.deinit();
try req.addHeader("X-Audit-Log-Reason", reason);
try req.delete(path);
}
/// Provide a user id to before and after for pagination.
/// Users will always be returned in ascending order by user.id.
/// If both before and after are provided, only before is respected.
pub const GetGuildBansQuery = struct {
limit: ?u16 = 1000,
before: ?Snowflake,
after: ?Snowflake,
};
/// Returns a list of ban objects for the users banned from this guild.
/// Requires the `BAN_MEMBERS` permission.
/// TODO: add query params
@ -1806,26 +1853,30 @@ pub fn fetchBan(self: *Self, guild_id: Snowflake, user_id: Snowflake) RequestFai
/// Requires the `BAN_MEMBERS` permission.
/// Returns a 204 empty response on success.
/// Fires a Guild Ban Add Gateway event.
pub fn ban(self: *Self, guild_id: Snowflake, user_id: Snowflake) RequestFailedError!void {
pub fn ban(self: *Self, guild_id: Snowflake, user_id: Snowflake, reason: ?[]const u8) RequestFailedError!void {
var buf: [256]u8 = undefined;
const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/bans/{d}", .{ guild_id.into(), user_id.into() });
var req = FetchReq.init(self.allocator, self.details.token);
defer req.deinit();
try req.addHeader("X-Audit-Log-Reason", reason);
try req.put3(path);
}
/// Remove the ban for a user. Requires the `BAN_MEMBERS` permissions.
/// Returns a 204 empty response on success.
/// Fires a Guild Ban Remove Gateway event.
pub fn unban(self: *Self, guild_id: Snowflake, user_id: Snowflake) RequestFailedError!void {
pub fn unban(self: *Self, guild_id: Snowflake, user_id: Snowflake, reason: ?[]const u8) RequestFailedError!void {
var buf: [256]u8 = undefined;
const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/bans/{d}", .{ guild_id.into(), user_id.into() });
var req = FetchReq.init(self.allocator, self.details.token);
defer req.deinit();
try req.addHeader("X-Audit-Log-Reason", reason);
try req.delete(path);
}
@ -1833,13 +1884,15 @@ pub fn unban(self: *Self, guild_id: Snowflake, user_id: Snowflake) RequestFailed
/// Requires both the `BAN_MEMBERS` and `MANAGE_GUILD` permissions.
/// Returns a 200 response on success, including the fields banned_users with the IDs of the banned users
/// and failed_users with IDs that could not be banned or were already banned.
pub fn bulkBan(self: *Self, guild_id: Snowflake, bulk_ban: Types.CreateGuildBan) RequestFailedError!zjson.Owned(Types.BulkBan) {
pub fn bulkBan(self: *Self, guild_id: Snowflake, bulk_ban: Types.CreateGuildBan, reason: ?[]const u8) RequestFailedError!zjson.Owned(Types.BulkBan) {
var buf: [256]u8 = undefined;
const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/bulk-ban", .{guild_id.into()});
var req = FetchReq.init(self.allocator, self.details.token);
defer req.deinit();
try req.addHeader("X-Audit-Log-Reason", reason);
const res = try req.post(Types.BulkBan, path, bulk_ban);
return res;
}
@ -1889,13 +1942,15 @@ pub fn createGuild(self: *Self, create_guild: Partial(Types.CreateGuild)) Reques
/// Returns the new role object on success.
/// Fires a Guild Role Create Gateway event.
/// All JSON params are optional.
pub fn createRole(self: *Self, guild_id: Snowflake, create_role: Partial(Types.CreateGuildRole)) RequestFailedError!zjson.Owned(Types.Role) {
pub fn createRole(self: *Self, guild_id: Snowflake, create_role: Partial(Types.CreateGuildRole), reason: ?[]const u8) RequestFailedError!zjson.Owned(Types.Role) {
var buf: [256]u8 = undefined;
const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/roles", .{guild_id.into()});
var req = FetchReq.init(self.allocator, self.details.token);
defer req.deinit();
try req.addHeader("X-Audit-Log-Reason", reason);
const res = try req.post(Types.Role, path, create_role);
return res;
}
@ -1904,13 +1959,21 @@ pub fn createRole(self: *Self, guild_id: Snowflake, create_role: Partial(Types.C
/// Requires the `MANAGE_ROLES` permission.
/// Returns a list of all of the guild's role objects on success.
/// Fires multiple Guild Role Update Gateway events.
pub fn editRole(self: *Self, guild_id: Snowflake, role_id: Snowflake, edit_role: Partial(Types.ModifyGuildRole)) RequestFailedError!zjson.Owned(Types.Role) {
pub fn editRole(
self: *Self,
guild_id: Snowflake,
role_id: Snowflake,
edit_role: Partial(Types.ModifyGuildRole),
reason: ?[]const u8,
) RequestFailedError!zjson.Owned(Types.Role) {
var buf: [256]u8 = undefined;
const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/roles/{d}", .{ guild_id.into(), role_id.into() });
var req = FetchReq.init(self.allocator, self.details.token);
defer req.deinit();
try req.addHeader("X-Audit-Log-Reason", reason);
const res = try req.patch(Types.Role, path, edit_role);
return res;
}
@ -1919,13 +1982,15 @@ pub fn editRole(self: *Self, guild_id: Snowflake, role_id: Snowflake, edit_role:
/// Requires guild ownership.
/// Returns the updated level on success.
/// Fires a Guild Update Gateway event.
pub fn modifyMFALevel(self: *Self, guild_id: Snowflake) RequestFailedError!void {
pub fn modifyMFALevel(self: *Self, guild_id: Snowflake, reason: ?[]const u8) RequestFailedError!void {
var buf: [256]u8 = undefined;
const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/mfa", .{guild_id.into()});
var req = FetchReq.init(self.allocator, self.details.token);
defer req.deinit();
try req.addHeader("X-Audit-Log-Reason", reason);
try req.delete(Types.Role, path);
}
@ -1933,13 +1998,15 @@ pub fn modifyMFALevel(self: *Self, guild_id: Snowflake) RequestFailedError!void
/// Requires the `MANAGE_ROLES` permission.
/// Returns a 204 empty response on success.
/// Fires a Guild Role Delete Gateway event.
pub fn deleteRole(self: *Self, guild_id: Snowflake, role_id: Snowflake) RequestFailedError!void {
pub fn deleteRole(self: *Self, guild_id: Snowflake, role_id: Snowflake, reason: ?[]const u8) RequestFailedError!void {
var buf: [256]u8 = undefined;
const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/roles/{d}", .{ guild_id.into(), role_id.into() });
var req = FetchReq.init(self.allocator, self.details.token);
defer req.deinit();
try req.addHeader("X-Audit-Log-Reason", reason);
try req.delete(Types.Role, path);
}
@ -1948,14 +2015,16 @@ pub fn deleteRole(self: *Self, guild_id: Snowflake, role_id: Snowflake) RequestF
/// By default, prune will not remove users with roles.
/// You can optionally include specific roles in your prune by providing the include_roles parameter.
/// Any inactive user that has a subset of the provided role(s) will be counted in the prune and users with additional roles will not.
/// TODO: implement query
pub fn fetchPruneCount(self: *Self, guild_id: Snowflake, _: Types.GetGuildPruneCountQuery) RequestFailedError!zjson.Owned(struct { pruned: isize }) {
pub fn fetchPruneCount(self: *Self, guild_id: Snowflake, query: Types.GetGuildPruneCountQuery) RequestFailedError!zjson.Owned(struct { pruned: isize }) {
var buf: [256]u8 = undefined;
const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/prune", .{guild_id.into()});
var req = FetchReq.init(self.allocator, self.details.token);
defer req.deinit();
try req.addQueryParam("days", query.days);
try req.addQueryParam("include_roles", query.include_roles); // needs fixing perhaps
const pruned = try req.get(struct { pruned: isize }, path);
return pruned;
}
@ -1969,13 +2038,20 @@ pub fn fetchPruneCount(self: *Self, guild_id: Snowflake, _: Types.GetGuildPruneC
/// By default, prune will not remove users with roles.
/// You can optionally include specific roles in your prune by providing the `include_roles` parameter.
/// Any inactive user that has a subset of the provided role(s) will be included in the prune and users with additional roles will not.
pub fn beginGuildPrune(self: *Self, guild_id: Snowflake, params: Types.BeginGuildPrune) RequestFailedError!zjson.Owned(struct { pruned: isize }) {
pub fn beginGuildPrune(
self: *Self,
guild_id: Snowflake,
params: Types.BeginGuildPrune,
reason: ?[]const u8,
) RequestFailedError!zjson.Owned(struct { pruned: isize }) {
var buf: [256]u8 = undefined;
const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/prune", .{guild_id.into()});
var req = FetchReq.init(self.allocator, self.details.token);
defer req.deinit();
try req.addHeader("X-Audit-Log-Reason", reason);
const pruned = try req.post(struct { pruned: isize }, path, params);
return pruned;
}
@ -2021,11 +2097,7 @@ pub fn fetchIntegrations(self: *Self, guild_id: Snowflake) RequestFailedError!zj
/// Returns a list of integration objects for the guild.
/// Requires the `MANAGE_GUILD` permission.
pub fn deleteIntegration(
self: *Self,
guild_id: Snowflake,
integration_id: Snowflake,
) RequestFailedError!void {
pub fn deleteIntegration(self: *Self, guild_id: Snowflake, integration_id: Snowflake, reason: ?[]const u8) RequestFailedError!void {
var buf: [256]u8 = undefined;
const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/integrations/{d}", .{
guild_id.into(),
@ -2035,6 +2107,8 @@ pub fn deleteIntegration(
var req = FetchReq.init(self.allocator, self.details.token);
defer req.deinit();
try req.addHeader("X-Audit-Log-Reason", reason);
try req.delete(path);
}
@ -2056,13 +2130,15 @@ pub fn fetchWidgetSettings(self: *Self, guild_id: Snowflake) RequestFailedError!
/// Requires the `MANAGE_GUILD` permission.
/// Returns the updated guild widget settings object.
/// Fires a Guild Update Gateway event.
pub fn editWidget(self: *Self, guild_id: Snowflake, attributes: Partial(Types.GuildWidget)) RequestFailedError!zjson.Owned(Types.GuildWidget) {
pub fn editWidget(self: *Self, guild_id: Snowflake, attributes: Partial(Types.GuildWidget), reason: ?[]const u8) RequestFailedError!zjson.Owned(Types.GuildWidget) {
var buf: [256]u8 = undefined;
const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/widget", .{guild_id.into()});
var req = FetchReq.init(self.allocator, self.details.token);
defer req.deinit();
try req.addHeader("X-Audit-Log-Reason", reason);
const widget = try req.patch(Types.GuildWidget, path, attributes);
return widget;
}
@ -2129,13 +2205,20 @@ pub fn fetchOnboarding(self: *Self, guild_id: Snowflake) RequestFailedError!zjso
}
/// Returns the Onboarding object for the guild.
pub fn editOnboarding(self: *Self, guild_id: Snowflake, onboarding: Types.GuildOnboardingPromptOption) RequestFailedError!zjson.Owned(Types.GuildOnboarding) {
pub fn editOnboarding(
self: *Self,
guild_id: Snowflake,
onboarding: Types.GuildOnboardingPromptOption,
reason: ?[]const u8,
) RequestFailedError!zjson.Owned(Types.GuildOnboarding) {
var buf: [256]u8 = undefined;
const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/onboarding", .{guild_id.into()});
var req = FetchReq.init(self.allocator, self.details.token);
defer req.deinit();
try req.addHeader("X-Audit-Log-Reason", reason);
const ob = try req.put(Types.GuildOnboarding, path, onboarding);
return ob;
}

View File

@ -159,7 +159,7 @@ pub const VoiceChannelEffectSend = struct {
/// 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) {
sound_id: union {
string: ?[]const u8,
integer: isize,
},
@ -168,11 +168,11 @@ pub const VoiceChannelEffectSend = struct {
};
/// https://discord.com/developers/docs/topics/gateway-events#voice-channel-effect-send-animation-types
pub const VoiceChannelEffectAnimationType = enum(u4) {
pub const VoiceChannelEffectAnimationType = enum {
/// A fun animation, sent by a Nitro subscriber
Premium = 0,
Premium,
/// The standard animation
Basic = 1,
Basic,
};
/// https://discord.com/developers/docs/topics/gateway#invite-create
@ -663,7 +663,7 @@ pub const CreateMessage = struct {
/// 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 } },
stickerIds: ?[][]const u8,
};
/// https://discord.com/developers/docs/resources/guild#modify-guild-welcome-screen

View File

@ -208,11 +208,7 @@ pub const InteractionDataOption = struct {
/// 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,
},
value: ?union { 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

View File

@ -95,7 +95,7 @@ pub const CreateTestEntitlement = struct {
/// ID of the guild or user to grant the entitlement top
owner_id: []const u8,
/// 1 for a guild subscription, 2 for a user subscription
owner_type: enum(u8) {
owner_type: enum(u4) {
guild_subscription = 1,
user_subscription = 2,
},

View File

@ -345,9 +345,9 @@ pub const ActivityFlags = packed struct {
};
/// https://discord.com/developers/docs/resources/guild#integration-object-integration-expire-behaviors
pub const IntegrationExpireBehaviors = enum(u4) {
RemoveRole = 0,
Kick = 1,
pub const IntegrationExpireBehaviors = enum {
RemoveRole,
Kick,
};
/// https://discord.com/developers/docs/topics/teams#data-models-membership-state-enum
@ -939,6 +939,7 @@ pub const ApplicationCommandPermissionTypes = enum(u4) {
};
/// https://discord.com/developers/docs/topics/permissions#permissions-bitwise-permission-flags
/// Permissions v2
pub const BitwisePermissionFlags = packed struct {
pub fn toRaw(self: BitwisePermissionFlags) u64 {
return @bitCast(self);
@ -1053,7 +1054,106 @@ pub const BitwisePermissionFlags = packed struct {
_pad: u15 = 0,
};
pub const PermissionStrings = BitwisePermissionFlags;
pub const PermissionStrings = union(enum) {
/// Allows creation of instant invites
CREATE_INSTANT_INVITE,
/// Allows kicking members
KICK_MEMBERS,
/// Allows banning members
BAN_MEMBERS,
/// Allows all permissions and bypasses channel permission overwrites
ADMINISTRATOR,
/// Allows management and editing of channels
MANAGE_CHANNELS,
/// Allows management and editing of the guild
MANAGE_GUILD,
/// Allows for the addition of reactions to messages
ADD_REACTIONS,
/// Allows for viewing of audit logs
VIEW_AUDIT_LOG,
/// Allows for using priority speaker in a voice channel
PRIORITY_SPEAKER,
/// Allows the user to go live
STREAM,
/// Allows guild members to view a channel, which includes reading messages in text channels and joining voice channels
VIEW_CHANNEL,
/// Allows for sending messages in a channel. (does not allow sending messages in threads)
SEND_MESSAGES,
/// Allows for sending of /tts messages
SEND_TTS_MESSAGES,
/// Allows for deletion of other users messages
MANAGE_MESSAGES,
/// Links sent by users with this permission will be auto-embedded
EMBED_LINKS,
/// Allows for uploading images and files
ATTACH_FILES,
/// Allows for reading of message history
READ_MESSAGE_HISTORY,
/// 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,
/// Allows the usage of custom emojis from other servers
USE_EXTERNAL_EMOJIS,
/// Allows for viewing guild insights
VIEW_GUILD_INSIGHTS,
/// Allows for joining of a voice channel
CONNECT,
/// Allows for speaking in a voice channel
SPEAK,
/// Allows for muting members in a voice channel
MUTE_MEMBERS,
/// Allows for deafening of members in a voice channel
DEAFEN_MEMBERS,
/// Allows for moving of members between voice channels
MOVE_MEMBERS,
/// Allows for using voice-activity-detection in a voice channel
USE_VAD,
/// Allows for modification of own nickname
CHANGE_NICKNAME,
/// Allows for modification of other users nicknames
MANAGE_NICKNAMES,
/// Allows management and editing of roles
MANAGE_ROLES,
/// Allows management and editing of webhooks
MANAGE_WEBHOOKS,
/// Allows for editing and deleting emojis, stickers, and soundboard sounds created by all users
MANAGE_GUILD_EXPRESSIONS,
/// Allows members to use application commands in text channels
USE_SLASH_COMMANDS,
/// Allows for requesting to speak in stage channels.
REQUEST_TO_SPEAK,
/// Allows for editing and deleting scheduled events created by all users
MANAGE_EVENTS,
/// Allows for deleting and archiving threads, and viewing all private threads
MANAGE_THREADS,
/// Allows for creating public and announcement threads
CREATE_PUBLIC_THREADS,
/// Allows for creating private threads
CREATE_PRIVATE_THREADS,
/// Allows the usage of custom stickers from other servers
USE_EXTERNAL_STICKERS,
/// Allows for sending messages in threads
SEND_MESSAGES_IN_THREADS,
/// Allows for launching activities (applications with the `EMBEDDED` flag) in a voice channel.
USE_EMBEDDED_ACTIVITIES,
/// 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,
/// Allows for viewing role subscription insights.
VIEW_CREATOR_MONETIZATION_ANALYTICS,
/// Allows for using soundboard in a voice channel.
USE_SOUNDBOARD,
/// Allows for creating emojis, stickers, and soundboard sounds, and editing and deleting those created by the current user
CREATE_GUILD_EXPRESSIONS,
/// Allows for creating scheduled events, and editing and deleting those created by the current user
CREATE_EVENTS,
/// Allows the usage of custom soundboards sounds from other servers
USE_EXTERNAL_SOUNDS,
/// Allows sending voice messages
SEND_VOICE_MESSAGES,
/// Allows sending polls
SEND_POLLS,
/// 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,
};
/// https://discord.com/developers/docs/topics/opcodes-and-status-codes#opcodes-and-status-codes
pub const GatewayCloseEventCodes = enum(u16) {
@ -1385,13 +1485,13 @@ pub const SortOrderTypes = enum {
CreationDate,
};
pub const ForumLayout = enum(u4) {
pub const ForumLayout = enum {
/// No default has been set for forum channel.
NotSet = 0,
NotSet,
/// Display posts as a list.
ListView = 1,
ListView,
/// Display posts as a collection of tiles.
GalleryView = 2,
GalleryView,
};
/// https://discord.com/developers/docs/reference#image-formatting
@ -1408,7 +1508,7 @@ pub const ImageFormat = union(enum) {
/// https://discord.com/developers/docs/reference#image-formatting
pub const ImageSize = isize;
pub const Locales = enum {
pub const Locales = union(enum) {
id,
da,
de,
@ -1444,7 +1544,7 @@ pub const Locales = enum {
};
/// https://discord.com/developers/docs/topics/oauth2#shared-resources-oauth2-scopes
pub const OAuth2Scope = enum {
pub const OAuth2Scope = union(enum) {
///
/// Allows your app to fetch data from a user's "Now Playing/Recently Played" list
///

View File

@ -17,6 +17,13 @@
const std = @import("std");
const zjson = @import("../json.zig");
/// Milliseconds since Discord Epoch, the first second of 2015 or 1420070400000.
pub const discord_epoch = 1420070400000;
/// Discord utilizes Twitter's snowflake format for uniquely identifiable descriptors (IDs).
/// These IDs are guaranteed to be unique across all of Discord, except in some unique scenarios in which child objects share their parent's ID.
/// Because Snowflake IDs are up to 64 bits in size (e.g. a uint64), they are always returned as strings in the HTTP API to prevent integer overflows in some languages.
/// See Gateway ETF/JSON for more information regarding Gateway encoding.
pub const Snowflake = enum(u64) {
_,
@ -46,14 +53,24 @@ pub const Snowflake = enum(u64) {
return array.toOwnedSlice();
}
/// zjson parse
pub fn toJson(_: std.mem.Allocator, value: zjson.JsonType) !@This() {
if (value.is(.string))
return Snowflake.fromRaw(value.string) catch std.debug.panic("invalid snowflake: {s}\n", .{value.string});
unreachable;
}
/// print
pub fn format(self: Snowflake, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
var buf: [256]u8 = undefined;
try writer.writeAll(try std.fmt.bufPrint(&buf, "{d}\n", .{self.into()}));
try writer.print("{d}", .{self.into()});
}
/// std.json stringify
pub fn jsonStringify(self: Snowflake, _: std.json.StringifyOptions, writer: anytype) !void {
try writer.print("\"{d}\"", .{self.into()});
}
pub fn toTimestamp(self: Snowflake) u64 {
return (self.into() >> 22) + discord_epoch;
}
};