minor refactor

This commit is contained in:
yuzu 2025-05-01 15:51:51 -05:00
parent 195c5945eb
commit b8c42bb22c
22 changed files with 1273 additions and 1268 deletions

View File

@ -17,7 +17,7 @@ pub fn build(b: *std.Build) void {
const zlib = b.dependency("zlib", .{});
const dzig = b.addModule("discord.zig", .{
.root_source_file = b.path("src/discord.zig"),
.root_source_file = b.path("src/lib.zig"),
.link_libc = true,
});
@ -47,7 +47,7 @@ pub fn build(b: *std.Build) void {
const lib = b.addStaticLibrary(.{
.name = "discord.zig",
.root_source_file = b.path("src/discord.zig"),
.root_source_file = b.path("lib/discord.zig"),
.target = target,
.optimize = optimize,
});

View File

@ -4,23 +4,23 @@ const std = @import("std");
/// otherwise, simply do TableTemplate{}
/// this is a template for the cache tables
pub const TableTemplate = struct {
comptime User: type = @import("./config.zig").StoredUser,
comptime Guild: type = @import("./config.zig").StoredGuild,
comptime Channel: type = @import("./config.zig").StoredChannel,
comptime Emoji: type = @import("./config.zig").StoredEmoji,
comptime Message: type = @import("./config.zig").StoredMessage,
comptime Role: type = @import("./config.zig").StoredRole,
comptime Sticker: type = @import("./config.zig").StoredSticker,
comptime Reaction: type = @import("./config.zig").StoredReaction,
comptime Member: type = @import("./config.zig").StoredMember,
comptime Thread: type = @import("./config.zig").StoredChannel,
comptime User: type = @import("../config.zig").StoredUser,
comptime Guild: type = @import("../config.zig").StoredGuild,
comptime Channel: type = @import("../config.zig").StoredChannel,
comptime Emoji: type = @import("../config.zig").StoredEmoji,
comptime Message: type = @import("../config.zig").StoredMessage,
comptime Role: type = @import("../config.zig").StoredRole,
comptime Sticker: type = @import("../config.zig").StoredSticker,
comptime Reaction: type = @import("../config.zig").StoredReaction,
comptime Member: type = @import("../config.zig").StoredMember,
comptime Thread: type = @import("../config.zig").StoredChannel,
};
// by default this caches everything
// therefore we'll allow custom cache tables
pub fn CacheTables(comptime Table: TableTemplate) type {
return struct {
const Snowflake = @import("./structures/snowflake.zig").Snowflake;
const Snowflake = @import("../structures/snowflake.zig").Snowflake;
const StoredUser: type = Table.User;
const StoredGuild: type = Table.Guild;

View File

@ -4,6 +4,7 @@ const PremiumTypes = @import("./structures/shared.zig").PremiumTypes;
const Snowflake = @import("./structures/snowflake.zig").Snowflake;
const AvatarDecorationData = @import("./structures/user.zig").AvatarDecorationData;
/// https://discord.com/developers/docs/resources/user#user-object
/// modify this to your liking
pub const StoredUser = struct {

View File

@ -29,5 +29,5 @@ pub const DiscordError = struct {
};
pub fn Result(comptime T: type) type {
return @import("json-helper.zig").OwnedEither(DiscordError, T);
return @import("./utils/json.zig").OwnedEither(DiscordError, T);
}

0
src/http/api.zig Normal file
View File

View File

@ -18,10 +18,10 @@ const std = @import("std");
const mem = std.mem;
const http = std.http;
const json = std.json;
const json_helpers = @import("json-helper.zig");
const json_helpers = @import("../utils/json.zig");
pub const Result = @import("errors.zig").Result;
pub const DiscordError = @import("errors.zig").DiscordError;
pub const Result = @import("../errors.zig").Result;
pub const DiscordError = @import("../errors.zig").DiscordError;
pub const BASE_URL = "https://discord.com/api/v10";

View File

@ -67,8 +67,6 @@ pub const PermissionStrings = @import("structures/types.zig").PermissionStrings;
pub const GatewayCloseEventCodes = @import("structures/types.zig").GatewayCloseEventCodes;
pub const GatewayOpcodes = @import("structures/types.zig").GatewayOpcodes;
pub const GatewayDispatchEventNames = @import("structures/types.zig").GatewayDispatchEventNames;
pub const GatewayIntents = @import("structures/types.zig").GatewayIntents;
pub const Intents = @import("structures/types.zig").Intents;
pub const InteractionResponseTypes = @import("structures/types.zig").InteractionResponseTypes;
pub const SortOrderTypes = @import("structures/types.zig").SortOrderTypes;
pub const ForumLayout = @import("structures/types.zig").ForumLayout;
@ -307,26 +305,26 @@ pub const ApplicationWebhook = @import("structures/types.zig").ApplicationWebhoo
pub const GatewayPayload = @import("structures/types.zig").GatewayPayload;
// END USING NAMESPACE
pub const CacheTables = @import("cache.zig").CacheTables;
pub const CacheLike = @import("cache.zig").CacheLike;
pub const DefaultCache = @import("cache.zig").DefaultCache;
pub const CacheTables = @import("cache/cache.zig").CacheTables;
pub const CacheLike = @import("cache/cache.zig").CacheLike;
pub const DefaultCache = @import("cache/cache.zig").DefaultCache;
pub const Permissions = @import("extra/permissions.zig").Permissions;
pub const Shard = @import("shard.zig").Shard;
pub const Permissions = @import("utils/permissions.zig").Permissions;
pub const Shard = @import("shard/shard.zig").Shard;
pub const zjson = @compileError("Deprecated.");
pub const Internal = @import("internal.zig");
pub const Internal = @import("utils/core.zig");
const GatewayDispatchEvent = Internal.GatewayDispatchEvent;
const GatewayBotInfo = Internal.GatewayBotInfo;
const GatewayBotInfo = @import("shard/util.zig").GatewayBotInfo;
const Log = Internal.Log;
// sharder
pub const Sharder = @import("sharder.zig").ShardManager;
pub const Sharder = @import("shard/sharder.zig").ShardManager;
pub const cache = @import("cache.zig");
pub const cache = @import("cache/cache.zig");
pub const FetchReq = @import("http.zig").FetchReq;
pub const FileData = @import("http.zig").FileData;
pub const FetchReq = @import("http/http.zig").FetchReq;
pub const FileData = @import("http/http.zig").FileData;
const std = @import("std");
const mem = std.mem;
@ -421,6 +419,9 @@ pub fn deinit(self: *Session) void {
self.deinit();
}
pub const GatewayIntents = @import("./shard/intents.zig").GatewayIntents;
pub const Intents = @import("./shard/intents.zig").Intents;
pub fn start(self: *Session, settings: struct {
token: []const u8,
intents: Intents,

106
src/shard/bucket.zig Normal file
View File

@ -0,0 +1,106 @@
const std = @import("std");
const mem = std.mem;
const math = std.math;
const time = std.time;
const atomic = std.atomic;
const Thread = std.Thread;
const PriorityQueue = std.PriorityQueue;
pub const Bucket = struct {
pub const RequestWithPrio = struct {
callback: *const fn () void,
priority: u32 = 1,
};
fn lessthan(_: void, a: RequestWithPrio, b: RequestWithPrio) math.Order {
return math.order(a.priority, b.priority);
}
/// The queue of requests to acquire an available request.
/// Mapped by (shardId, RequestWithPrio)
queue: PriorityQueue(RequestWithPrio, void, lessthan),
limit: usize,
refill_interval: u64,
refill_amount: usize,
/// The amount of requests that have been used up already.
used: usize = 0,
/// Whether or not the queue is already processing.
processing: bool = false,
/// Whether the timeout should be killed because there is already one running
should_stop: atomic.Value(bool) = .init(false),
/// The timestamp in milliseconds when the next refill is scheduled.
refills_at: ?u64 = null,
pub fn init(allocator: mem.Allocator, limit: usize, refill_interval: u64, refill_amount: usize) Bucket {
return .{
.queue = PriorityQueue(RequestWithPrio, void, lessthan).init(allocator, {}),
.limit = limit,
.refill_interval = refill_interval,
.refill_amount = refill_amount,
};
}
fn remaining(self: *Bucket) usize {
if (self.limit < self.used) {
return 0;
} else {
return self.limit - self.used;
}
}
pub fn refill(self: *Bucket) Thread.SpawnError!void {
// Lower the used amount by the refill amount
self.used = if (self.refill_amount > self.used) 0 else self.used - self.refill_amount;
// Reset the refills_at timestamp since it just got refilled
self.refills_at = null;
if (self.used > 0) {
if (self.should_stop.load(.monotonic) == true) {
self.should_stop.store(false, .monotonic);
}
const thread = try Thread.spawn(.{}, Bucket.timeout, .{self});
thread.detach;
self.refills_at = time.milliTimestamp() + self.refill_interval;
}
}
fn timeout(self: *Bucket) void {
while (!self.should_stop.load(.monotonic)) {
self.refill();
time.sleep(time.ns_per_ms * self.refill_interval);
}
}
pub fn processQueue(self: *Bucket) std.Thread.SpawnError!void {
if (self.processing) return;
while (self.queue.remove()) |first_element| {
if (self.remaining() != 0) {
first_element.callback();
self.used += 1;
if (!self.should_stop.load(.monotonic)) {
const thread = try Thread.spawn(.{}, Bucket.timeout, .{self});
thread.detach;
self.refills_at = time.milliTimestamp() + self.refill_interval;
}
} else if (self.refills_at) |ra| {
const now = time.milliTimestamp();
if (ra > now) time.sleep(time.ns_per_ms * (ra - now));
}
}
self.processing = false;
}
pub fn acquire(self: *Bucket, rq: RequestWithPrio) !void {
try self.queue.add(rq);
try self.processQueue();
}
};

View File

@ -0,0 +1,87 @@
const std = @import("std");
const mem = std.mem;
/// inspired from:
/// https://github.com/tiramisulabs/seyfert/blob/main/src/websocket/structures/timeout.ts
pub fn ConnectQueue(comptime T: type) type {
return struct {
pub const RequestWithShard = struct {
callback: *const fn (self: *RequestWithShard) anyerror!void,
shard: T,
};
// ignore this function
// so it becomes a regular dequeue
fn eq(_: void, _: RequestWithShard, _: RequestWithShard) std.math.Order {
return std.math.Order.eq;
}
dequeue: std.PriorityDequeue(RequestWithShard, void, eq),
allocator: mem.Allocator,
remaining: usize,
interval_time: u64 = 5000,
running: bool = false,
concurrency: usize = 1,
pub fn init(allocator: mem.Allocator, concurrency: usize, interval_time: u64) !ConnectQueue(T) {
return .{
.allocator = allocator,
.dequeue = std.PriorityDequeue(RequestWithShard, void, eq).init(allocator, {}),
.remaining = concurrency,
.interval_time = interval_time,
.concurrency = concurrency,
};
}
pub fn deinit(self: *ConnectQueue(T)) void {
self.dequeue.deinit();
}
pub fn push(self: *ConnectQueue(T), req: RequestWithShard) !void {
if (self.remaining == 0) {
return self.dequeue.add(req);
}
self.remaining -= 1;
if (!self.running) {
try self.startInterval();
self.running = true;
}
if (self.dequeue.count() < self.concurrency) {
// perhaps store this?
const ptr = try self.allocator.create(RequestWithShard);
ptr.* = req;
try req.callback(ptr);
return;
}
return self.dequeue.add(req);
}
fn startInterval(self: *ConnectQueue(T)) !void {
while (self.running) {
std.Thread.sleep(std.time.ns_per_ms * (self.interval_time / self.concurrency));
const req: ?RequestWithShard = self.dequeue.removeMin(); // pop front
while (self.dequeue.count() == 0 and req == null) {}
if (req) |r| {
const ptr = try self.allocator.create(RequestWithShard);
ptr.* = r;
try @call(.auto, r.callback, .{ptr});
return;
}
if (self.remaining < self.concurrency) {
self.remaining += 1;
}
if (self.dequeue.count() == 0) {
self.running = false;
}
}
}
};
}

179
src/shard/intents.zig Normal file
View File

@ -0,0 +1,179 @@
const std = @import("std");
const mem = std.mem;
const json = std.json;
/// https://discord.com/developers/docs/topics/gateway#list-of-intents
pub const GatewayIntents = packed struct {
pub fn toRaw(self: GatewayIntents) u32 {
return @bitCast(self);
}
pub fn fromRaw(raw: u32) GatewayIntents {
return @bitCast(raw);
}
pub fn jsonParse(allocator: mem.Allocator, src: anytype, _: json.ParseOptions) !@This() {
const value = try json.innerParse(json.Value, allocator, src, .{
.ignore_unknown_fields = true,
.max_value_len = 0x1000,
});
if (value != .integer) @panic("Invalid value for bitfield");
return fromRaw(@intCast(value.integer));
}
pub fn jsonParseFromValue(_: mem.Allocator, src: json.Value, _: json.ParseOptions) @This() {
if (src != .integer) @panic("Invalid value for bitfield");
return fromRaw(@intCast(src.integer));
}
///
/// - GUILD_CREATE
/// - GUILD_UPDATE
/// - GUILD_DELETE
/// - GUILD_ROLE_CREATE
/// - GUILD_ROLE_UPDATE
/// - GUILD_ROLE_DELETE
/// - CHANNEL_CREATE
/// - CHANNEL_UPDATE
/// - CHANNEL_DELETE
/// - CHANNEL_PINS_UPDATE
/// - THREAD_CREATE
/// - THREAD_UPDATE
/// - THREAD_DELETE
/// - THREAD_LIST_SYNC
/// - THREAD_MEMBER_UPDATE
/// - THREAD_MEMBERS_UPDATE
/// - STAGE_INSTANCE_CREATE
/// - STAGE_INSTANCE_UPDATE
/// - STAGE_INSTANCE_DELETE
////
Guilds: bool = false,
///
/// - GUILD_MEMBER_ADD
/// - GUILD_MEMBER_UPDATE
/// - GUILD_MEMBER_REMOVE
/// - THREAD_MEMBERS_UPDATE
///
/// This is a privileged intent.
////
GuildMembers: bool = false,
///
/// - GUILD_AUDIT_LOG_ENTRY_CREATE
/// - GUILD_BAN_ADD
/// - GUILD_BAN_REMOVE
////
GuildModeration: bool = false,
///
/// - GUILD_EMOJIS_UPDATE
/// - GUILD_STICKERS_UPDATE
////
GuildEmojisAndStickers: bool = false,
///
/// - GUILD_INTEGRATIONS_UPDATE
/// - INTEGRATION_CREATE
/// - INTEGRATION_UPDATE
/// - INTEGRATION_DELETE
////
GuildIntegrations: bool = false,
///
/// - WEBHOOKS_UPDATE
////
GuildWebhooks: bool = false,
///
/// - INVITE_CREATE
/// - INVITE_DELETE
////
GuildInvites: bool = false,
///
/// - VOICE_STATE_UPDATE
/// - VOICE_CHANNEL_EFFECT_SEND
////
GuildVoiceStates: bool = false,
///
/// - PRESENCE_UPDATE
///
/// This is a privileged intent.
////
GuildPresences: bool = false,
///
/// - MESSAGE_CREATE
/// - MESSAGE_UPDATE
/// - MESSAGE_DELETE
/// - MESSAGE_DELETE_BULK
///
/// The messages do not contain content by default.
/// If you want to receive their content too, you need to turn on the privileged `MESSAGE_CONTENT` intent. */
GuildMessages: bool = false,
///
/// - MESSAGE_REACTION_ADD
/// - MESSAGE_REACTION_REMOVE
/// - MESSAGE_REACTION_REMOVE_ALL
/// - MESSAGE_REACTION_REMOVE_EMOJI
////
GuildMessageReactions: bool = false,
///
/// - TYPING_START
////
GuildMessageTyping: bool = false,
///
/// - CHANNEL_CREATE
/// - MESSAGE_CREATE
/// - MESSAGE_UPDATE
/// - MESSAGE_DELETE
/// - CHANNEL_PINS_UPDATE
////
DirectMessages: bool = false,
///
/// - MESSAGE_REACTION_ADD
/// - MESSAGE_REACTION_REMOVE
/// - MESSAGE_REACTION_REMOVE_ALL
/// - MESSAGE_REACTION_REMOVE_EMOJI
////
DirectMessageReactions: bool = false,
///
/// - TYPING_START
////
DirectMessageTyping: bool = false,
///
/// This intent will add all content related values to message events.
///
/// This is a privileged intent.
////
MessageContent: bool = false,
///
/// - GUILD_SCHEDULED_EVENT_CREATE
/// - GUILD_SCHEDULED_EVENT_UPDATE
/// - GUILD_SCHEDULED_EVENT_DELETE
/// - GUILD_SCHEDULED_EVENT_USER_ADD this is experimental and unstable.
/// - GUILD_SCHEDULED_EVENT_USER_REMOVE this is experimental and unstable.
////
GuildScheduledEvents: bool = false,
_pad: u4 = 0,
///
/// - AUTO_MODERATION_RULE_CREATE
/// - AUTO_MODERATION_RULE_UPDATE
/// - AUTO_MODERATION_RULE_DELETE
////
AutoModerationConfiguration: bool = false,
///
/// - AUTO_MODERATION_ACTION_EXECUTION
////
AutoModerationExecution: bool = false,
_pad2: u3 = 0,
///
/// - MESSAGE_POLL_VOTE_ADD
/// - MESSAGE_POLL_VOTE_REMOVE
////
GuildMessagePolls: bool = false,
///
/// - MESSAGE_POLL_VOTE_ADD
/// - MESSAGE_POLL_VOTE_REMOVE
////
DirectMessagePolls: bool = false,
_pad3: u4 = 0,
};
/// https://discord.com/developers/docs/topics/gateway#list-of-intents
/// alias
pub const Intents = GatewayIntents;

View File

@ -28,29 +28,31 @@ const http = std.http;
const zlib = @import("zlib");
const json = @import("std").json;
const Result = @import("errors.zig").Result;
const Result = @import("../errors.zig").Result;
const IdentifyProperties = @import("internal.zig").IdentifyProperties;
const GatewayInfo = @import("internal.zig").GatewayInfo;
const GatewayBotInfo = @import("internal.zig").GatewayBotInfo;
const GatewaySessionStartLimit = @import("internal.zig").GatewaySessionStartLimit;
const ShardDetails = @import("internal.zig").ShardDetails;
const internalLogif = @import("internal.zig").logif;
const IdentifyProperties = @import("util.zig").IdentifyProperties;
const GatewayInfo = @import("util.zig").GatewayInfo;
const GatewayBotInfo = @import("util.zig").GatewayBotInfo;
const GatewaySessionStartLimit = @import("util.zig").GatewaySessionStartLimit;
const ShardDetails = @import("util.zig").ShardDetails;
const internalLogif = @import("../utils/core.zig").logif;
const Log = @import("internal.zig").Log;
const GatewayDispatchEvent = @import("internal.zig").GatewayDispatchEvent;
const Bucket = @import("internal.zig").Bucket;
const default_identify_properties = @import("internal.zig").default_identify_properties;
const Log = @import("../utils/core.zig").Log;
const GatewayDispatchEvent = @import("../utils/core.zig").GatewayDispatchEvent;
const Bucket = @import("bucket.zig").Bucket;
const default_identify_properties = @import("util.zig").default_identify_properties;
const Types = @import("./structures/types.zig");
const Types = @import("../structures/types.zig");
const Opcode = Types.GatewayOpcodes;
const Intents = Types.Intents;
const Intents = @import("intents.zig").Intents;
const Snowflake = Types.Snowflake;
const FetchReq = @import("http.zig").FetchReq;
const MakeRequestError = @import("http.zig").MakeRequestError;
const FetchReq = @import("../http/http.zig").FetchReq;
const MakeRequestError = @import("../http/http.zig").MakeRequestError;
const Partial = Types.Partial;
const TableTemplate = @import("cache.zig").TableTemplate;
const TableTemplate = @import("../cache/cache.zig").TableTemplate;
const CacheTables = @import("../cache/cache.zig").CacheTables;
const FileData = @import("../http/http.zig").FileData;
pub fn Shard(comptime Table: TableTemplate) type {
return struct {
@ -106,7 +108,7 @@ pub fn Shard(comptime Table: TableTemplate) type {
log: Log = .no,
/// actual cache
cache_handler: *@import("cache.zig").CacheTables(Table),
cache_handler: *CacheTables(Table),
pub fn resumable(self: *Self) bool {
return self.resume_gateway_url != null and
@ -160,7 +162,7 @@ pub fn Shard(comptime Table: TableTemplate) type {
options: ShardOptions,
run: GatewayDispatchEvent,
log: Log,
cache: *@import("cache.zig").CacheTables(Table),
cache: *CacheTables(Table),
sharder_pool: ?*std.Thread.Pool = null,
}) zlib.Error!Self {
return Self{
@ -933,7 +935,7 @@ pub fn Shard(comptime Table: TableTemplate) type {
/// the create message payload
create_message: Partial(Types.CreateMessage),
/// the files to send, must be one of FileData.type
files: []@import("http.zig").FileData,
files: []FileData,
};
/// same as `sendMessage` but acceps a files field
@ -1448,7 +1450,7 @@ pub fn Shard(comptime Table: TableTemplate) type {
pub const StartThreadInForumOrMediaChannelWithFiles = struct {
start_thread: Types.StartThreadFromMessage,
files: []@import("http.zig").FileData,
files: []FileData,
};
/// same as `startThreadInForumOrMediaChannel`
@ -2862,7 +2864,7 @@ pub fn Shard(comptime Table: TableTemplate) type {
self: *Self,
guild_id: Snowflake,
sticker: Types.CreateModifyGuildSticker,
file: @import("http.zig").FileData,
file: FileData,
) RequestFailedError!Result(Types.Sticker) {
var buf: [256]u8 = undefined;
const path = try std.fmt.bufPrint(&buf, "/guilds/{d}/stickers", .{guild_id.into()});

View File

@ -14,20 +14,21 @@
//! OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
//! PERFORMANCE OF THIS SOFTWARE.
const Intents = @import("./structures/types.zig").Intents;
const Snowflake = @import("./structures/snowflake.zig").Snowflake;
const GatewayBotInfo = @import("internal.zig").GatewayBotInfo;
const IdentifyProperties = @import("internal.zig").IdentifyProperties;
const internalLogif = @import("internal.zig").logif;
const ShardDetails = @import("internal.zig").ShardDetails;
const ConnectQueue = @import("internal.zig").ConnectQueue;
const GatewayDispatchEvent = @import("internal.zig").GatewayDispatchEvent;
const Log = @import("internal.zig").Log;
const Intents = @import("intents.zig").Intents;
const Snowflake = @import("../structures/snowflake.zig").Snowflake;
const GatewayBotInfo = @import("util.zig").GatewayBotInfo;
const IdentifyProperties = @import("util.zig").IdentifyProperties;
const internalLogif = @import("../utils/core.zig").logif;
const ShardDetails = @import("util.zig").ShardDetails;
const ConnectQueue = @import("connect_queue.zig").ConnectQueue;
const GatewayDispatchEvent = @import("../utils/core.zig").GatewayDispatchEvent;
const Log = @import("../utils/core.zig").Log;
const Shard = @import("shard.zig").Shard;
const std = @import("std");
const mem = std.mem;
const debug = @import("internal.zig").debug;
const TableTemplate = @import("cache.zig").TableTemplate;
const debug = @import("../utils/core.zig").debug;
const TableTemplate = @import("../cache/cache.zig").TableTemplate;
const CacheTables = @import("../cache/cache.zig").CacheTables;
pub fn ShardManager(comptime Table: TableTemplate) type {
return struct {
@ -50,7 +51,7 @@ pub fn ShardManager(comptime Table: TableTemplate) type {
log: Log,
// must be initialised
cache: *@import("cache.zig").CacheTables(Table),
cache: *CacheTables(Table),
pub const ShardData = struct {
/// resume seq to resume connections
@ -87,11 +88,11 @@ pub fn ShardManager(comptime Table: TableTemplate) type {
options: SessionOptions,
run: GatewayDispatchEvent,
log: Log,
cache: @import("cache.zig").TableTemplate,
cache: TableTemplate,
}) mem.Allocator.Error!Self {
const concurrency = settings.options.info.session_start_limit.?.max_concurrency;
const cache = try allocator.create(@import("cache.zig").CacheTables(Table));
cache.* = @import("cache.zig").CacheTables(Table).defaults(allocator);
const cache = try allocator.create(CacheTables(Table));
cache.* = CacheTables(Table).defaults(allocator);
return .{
.allocator = allocator,

59
src/shard/util.zig Normal file
View File

@ -0,0 +1,59 @@
pub const IdentifyProperties = struct {
/// Operating system the shard runs on.
os: []const u8,
/// The "browser" where this shard is running on.
browser: []const u8,
/// The device on which the shard is running.
device: []const u8,
};
pub const default_identify_properties = IdentifyProperties{
.os = @tagName(@import("builtin").os.tag),
.browser = "discord.zig",
.device = "discord.zig",
};
/// https://discord.com/developers/docs/topics/gateway#get-gateway
pub const GatewayInfo = struct {
/// The WSS URL that can be used for connecting to the gateway
url: []const u8,
};
/// https://discord.com/developers/docs/events/gateway#session-start-limit-object
pub const GatewaySessionStartLimit = struct {
/// Total number of session starts the current user is allowed
total: u32,
/// Remaining number of session starts the current user is allowed
remaining: u32,
/// Number of milliseconds after which the limit resets
reset_after: u32,
/// Number of identify requests allowed per 5 seconds
max_concurrency: u32,
};
/// https://discord.com/developers/docs/topics/gateway#get-gateway-bot
pub const GatewayBotInfo = struct {
url: []const u8,
/// The recommended number of shards to use when connecting
///
/// See https://discord.com/developers/docs/topics/gateway#sharding
shards: u32,
/// Information on the current session start limit
///
/// See https://discord.com/developers/docs/topics/gateway#session-start-limit-object
session_start_limit: ?GatewaySessionStartLimit,
};
pub const ShardDetails = struct {
/// Bot token which is used to connect to Discord */
token: []const u8,
/// The URL of the gateway which should be connected to.
url: []const u8 = "wss://gateway.discord.gg",
/// The gateway version which should be used.
version: ?usize = 10,
/// The calculated intent value of the events which the shard should receive.
intents: @import("./intents.zig").Intents,
/// Identify properties to use
properties: IdentifyProperties = default_identify_properties,
};

View File

@ -21,7 +21,7 @@ const Partial = @import("partial.zig").Partial;
const User = @import("user.zig").User;
const Team = @import("team.zig").Team;
const Guild = @import("guild.zig").Guild;
const AssociativeArray = @import("../json-helper.zig").AssociativeArray;
const AssociativeArray = @import("../utils/json.zig").AssociativeArray;
/// https://discord.com/developers/docs/resources/application#application-object
pub const Application = struct {

View File

@ -32,7 +32,7 @@
const InteractionResponseTypes = @import("shared.zig").InteractionResponseTypes;
const InteractionContextType = @import("command.zig").InteractionContextType;
const Entitlement = @import("monetization.zig").Entitlement;
const Record = @import("../json-helper.zig").Record;
const Record = @import("../utils/json.zig").Record;
pub const Interaction = struct {
/// Id of the interaction

View File

@ -15,12 +15,13 @@
//! PERFORMANCE OF THIS SOFTWARE.
const std = @import("std");
const builtin = std.builtin;
pub fn Partial(comptime T: type) type {
const info = @typeInfo(T);
switch (info) {
.@"struct" => |s| {
comptime var fields: []const std.builtin.Type.StructField = &[_]std.builtin.Type.StructField{};
comptime var fields: []const builtin.Type.StructField = &[_]builtin.Type.StructField{};
inline for (s.fields) |field| {
if (field.is_comptime) {
@compileError("Cannot make Partial of " ++ @typeName(T) ++ ", it has a comptime field " ++ field.name);
@ -31,7 +32,7 @@ pub fn Partial(comptime T: type) type {
};
const default_value: optional_type = null;
const aligned_ptr: *align(field.alignment) const anyopaque = @alignCast(@ptrCast(&default_value));
const optional_field: [1]std.builtin.Type.StructField = [_]std.builtin.Type.StructField{.{
const optional_field: [1]builtin.Type.StructField = [_]builtin.Type.StructField{.{
.alignment = field.alignment,
.default_value_ptr = aligned_ptr,
.is_comptime = false,
@ -40,9 +41,9 @@ pub fn Partial(comptime T: type) type {
}};
fields = fields ++ optional_field;
}
const partial_type_info: std.builtin.Type = .{ .@"struct" = .{
const partial_type_info: builtin.Type = .{ .@"struct" = .{
.backing_integer = s.backing_integer,
.decls = &[_]std.builtin.Type.Declaration{},
.decls = &[_]builtin.Type.Declaration{},
.fields = fields,
.is_tuple = s.is_tuple,
.layout = s.layout,

File diff suppressed because it is too large Load Diff

View File

@ -66,8 +66,6 @@
pub const GatewayCloseEventCodes = @import("shared.zig").GatewayCloseEventCodes;
pub const GatewayOpcodes = @import("shared.zig").GatewayOpcodes;
pub const GatewayDispatchEventNames = @import("shared.zig").GatewayDispatchEventNames;
pub const GatewayIntents = @import("shared.zig").GatewayIntents;
pub const Intents = @import("shared.zig").Intents;
pub const InteractionResponseTypes = @import("shared.zig").InteractionResponseTypes;
pub const SortOrderTypes = @import("shared.zig").SortOrderTypes;
pub const ForumLayout = @import("shared.zig").ForumLayout;

View File

@ -20,7 +20,7 @@
const OAuth2Scope = @import("shared.zig").OAuth2Scope;
const Integration = @import("integration.zig").Integration;
const Partial = @import("partial.zig").Partial;
const Record = @import("../json-helper.zig").Record;
const Record = @import("../utils/json.zig").Record;
/// https://discord.com/developers/docs/resources/user#user-object
pub const User = struct {

View File

@ -15,74 +15,7 @@
//! PERFORMANCE OF THIS SOFTWARE.
const std = @import("std");
const mem = std.mem;
const builtin = @import("builtin");
const Types = @import("./structures/types.zig");
pub const IdentifyProperties = struct {
/// Operating system the shard runs on.
os: []const u8,
/// The "browser" where this shard is running on.
browser: []const u8,
/// The device on which the shard is running.
device: []const u8,
system_locale: ?[]const u8 = null, // TODO parse this
browser_user_agent: ?[]const u8 = null,
browser_version: ?[]const u8 = null,
os_version: ?[]const u8 = null,
referrer: ?[]const u8 = null,
referring_domain: ?[]const u8 = null,
referrer_current: ?[]const u8 = null,
referring_domain_current: ?[]const u8 = null,
release_channel: ?[]const u8 = null,
client_build_number: ?u64 = null,
client_event_source: ?[]const u8 = null,
};
/// https://discord.com/developers/docs/topics/gateway#get-gateway
pub const GatewayInfo = struct {
/// The WSS URL that can be used for connecting to the gateway
url: []const u8,
};
/// https://discord.com/developers/docs/events/gateway#session-start-limit-object
pub const GatewaySessionStartLimit = struct {
/// Total number of session starts the current user is allowed
total: u32,
/// Remaining number of session starts the current user is allowed
remaining: u32,
/// Number of milliseconds after which the limit resets
reset_after: u32,
/// Number of identify requests allowed per 5 seconds
max_concurrency: u32,
};
/// https://discord.com/developers/docs/topics/gateway#get-gateway-bot
pub const GatewayBotInfo = struct {
url: []const u8,
/// The recommended number of shards to use when connecting
///
/// See https://discord.com/developers/docs/topics/gateway#sharding
shards: u32,
/// Information on the current session start limit
///
/// See https://discord.com/developers/docs/topics/gateway#session-start-limit-object
session_start_limit: ?GatewaySessionStartLimit,
};
pub const ShardDetails = struct {
/// Bot token which is used to connect to Discord */
token: []const u8,
/// The URL of the gateway which should be connected to.
url: []const u8 = "wss://gateway.discord.gg",
/// The gateway version which should be used.
version: ?usize = 10,
/// The calculated intent value of the events which the shard should receive.
intents: Types.Intents,
/// Identify properties to use
properties: IdentifyProperties = default_identify_properties,
};
const Types = @import("../structures/types.zig");
pub const debug = std.log.scoped(.@"discord.zig");
@ -95,194 +28,6 @@ pub inline fn logif(log: Log, comptime format: []const u8, args: anytype) void {
}
}
pub const default_identify_properties = IdentifyProperties{
.os = @tagName(builtin.os.tag),
.browser = "discord.zig",
.device = "discord.zig",
};
/// inspired from:
/// https://github.com/tiramisulabs/seyfert/blob/main/src/websocket/structures/timeout.ts
pub fn ConnectQueue(comptime T: type) type {
return struct {
pub const RequestWithShard = struct {
callback: *const fn (self: *RequestWithShard) anyerror!void,
shard: T,
};
// ignore this function
// so it becomes a regular dequeue
fn eq(_: void, _: RequestWithShard, _: RequestWithShard) std.math.Order {
return std.math.Order.eq;
}
dequeue: std.PriorityDequeue(RequestWithShard, void, eq),
allocator: mem.Allocator,
remaining: usize,
interval_time: u64 = 5000,
running: bool = false,
concurrency: usize = 1,
pub fn init(allocator: mem.Allocator, concurrency: usize, interval_time: u64) !ConnectQueue(T) {
return .{
.allocator = allocator,
.dequeue = std.PriorityDequeue(RequestWithShard, void, eq).init(allocator, {}),
.remaining = concurrency,
.interval_time = interval_time,
.concurrency = concurrency,
};
}
pub fn deinit(self: *ConnectQueue(T)) void {
self.dequeue.deinit();
}
pub fn push(self: *ConnectQueue(T), req: RequestWithShard) !void {
if (self.remaining == 0) {
return self.dequeue.add(req);
}
self.remaining -= 1;
if (!self.running) {
try self.startInterval();
self.running = true;
}
if (self.dequeue.count() < self.concurrency) {
// perhaps store this?
const ptr = try self.allocator.create(RequestWithShard);
ptr.* = req;
try req.callback(ptr);
return;
}
return self.dequeue.add(req);
}
fn startInterval(self: *ConnectQueue(T)) !void {
while (self.running) {
std.Thread.sleep(std.time.ns_per_ms * (self.interval_time / self.concurrency));
const req: ?RequestWithShard = self.dequeue.removeMin(); // pop front
while (self.dequeue.count() == 0 and req == null) {}
if (req) |r| {
const ptr = try self.allocator.create(RequestWithShard);
ptr.* = r;
try @call(.auto, r.callback, .{ptr});
return;
}
if (self.remaining < self.concurrency) {
self.remaining += 1;
}
if (self.dequeue.count() == 0) {
self.running = false;
}
}
}
};
}
pub const Bucket = struct {
/// The queue of requests to acquire an available request. Mapped by (shardId, RequestWithPrio)
queue: std.PriorityQueue(RequestWithPrio, void, Bucket.lessthan),
limit: usize,
refill_interval: u64,
refill_amount: usize,
/// The amount of requests that have been used up already.
used: usize = 0,
/// Whether or not the queue is already processing.
processing: bool = false,
/// Whether the timeout should be killed because there is already one running
should_stop: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
/// The timestamp in milliseconds when the next refill is scheduled.
refills_at: ?u64 = null,
pub const RequestWithPrio = struct {
callback: *const fn () void,
priority: u32 = 1,
};
fn lessthan(_: void, a: RequestWithPrio, b: RequestWithPrio) std.math.Order {
return std.math.order(a.priority, b.priority);
}
pub fn init(allocator: mem.Allocator, limit: usize, refill_interval: u64, refill_amount: usize) Bucket {
return .{
.queue = std.PriorityQueue(RequestWithPrio, void, lessthan).init(allocator, {}),
.limit = limit,
.refill_interval = refill_interval,
.refill_amount = refill_amount,
};
}
fn remaining(self: *Bucket) usize {
if (self.limit < self.used) {
return 0;
} else {
return self.limit - self.used;
}
}
pub fn refill(self: *Bucket) std.Thread.SpawnError!void {
// Lower the used amount by the refill amount
self.used = if (self.refill_amount > self.used) 0 else self.used - self.refill_amount;
// Reset the refills_at timestamp since it just got refilled
self.refills_at = null;
if (self.used > 0) {
if (self.should_stop.load(.monotonic) == true) {
self.should_stop.store(false, .monotonic);
}
const thread = try std.Thread.spawn(.{}, Bucket.timeout, .{self});
thread.detach;
self.refills_at = std.time.milliTimestamp() + self.refill_interval;
}
}
fn timeout(self: *Bucket) void {
while (!self.should_stop.load(.monotonic)) {
self.refill();
std.time.sleep(std.time.ns_per_ms * self.refill_interval);
}
}
pub fn processQueue(self: *Bucket) std.Thread.SpawnError!void {
if (self.processing) return;
while (self.queue.remove()) |first_element| {
if (self.remaining() != 0) {
first_element.callback();
self.used += 1;
if (!self.should_stop.load(.monotonic)) {
const thread = try std.Thread.spawn(.{}, Bucket.timeout, .{self});
thread.detach;
self.refills_at = std.time.milliTimestamp() + self.refill_interval;
}
} else if (self.refills_at) |ra| {
const now = std.time.milliTimestamp();
if (ra > now) std.time.sleep(std.time.ns_per_ms * (ra - now));
}
}
self.processing = false;
}
pub fn acquire(self: *Bucket, rq: RequestWithPrio) !void {
try self.queue.add(rq);
try self.processQueue();
}
};
pub const GatewayDispatchEvent = struct {
application_command_permissions_update: ?*const fn (save: *anyopaque, application_command_permissions: Types.ApplicationCommandPermissions) anyerror!void = undefined,
auto_moderation_rule_create: ?*const fn (save: *anyopaque, rule: Types.AutoModerationRule) anyerror!void = undefined,