started working

This commit is contained in:
rainfall 2024-11-01 23:49:58 -05:00
parent c08feecab9
commit fa2243b097
3 changed files with 171 additions and 73 deletions

View File

@ -11,21 +11,12 @@ const tls = std.crypto.tls;
// todo use this to read compressed messages
const zlib = @import("zlib");
const Discord = @import("types.zig");
const Self = @This();
const Opcode = enum(u4) {
Dispatch = 0,
Heartbeat = 1,
Identify = 2,
PresenceUpdate = 3,
VoiceStateUpdate = 4,
Resume = 6,
Reconnect = 7,
RequestGuildMember = 8,
InvalidSession = 9,
Hello = 10,
HeartbeatACK = 11,
};
const GatewayPayload = Discord.GatewayPayload;
const Opcode = Discord.GatewayOpcodes;
const ShardSocketCloseCodes = enum(u16) {
Shutdown = 3000,
@ -75,6 +66,78 @@ pub const Intents = packed struct {
}
};
pub const GatewayDispatchEvent = struct {
// TODO: implement // application_command_permissions_update: null = null,
// TODO: implement // auto_moderation_rule_create: null = null,
// TODO: implement // auto_moderation_rule_update: null = null,
// TODO: implement // auto_moderation_rule_delete: null = null,
// TODO: implement // auto_moderation_action_execution: null = null,
// TODO: implement // channel_create: null = null,
// TODO: implement // channel_update: null = null,
// TODO: implement // channel_delete: null = null,
// TODO: implement // channel_pins_update: null = null,
// TODO: implement // thread_create: null = null,
// TODO: implement // thread_update: null = null,
// TODO: implement // thread_delete: null = null,
// TODO: implement // thread_list_sync: null = null,
// TODO: implement // thread_member_update: null = null,
// TODO: implement // thread_members_update: null = null,
// TODO: implement // guild_audit_log_entry_create: null = null,
// TODO: implement // guild_create: null = null,
// TODO: implement // guild_update: null = null,
// TODO: implement // guild_delete: null = null,
// TODO: implement // guild_ban_add: null = null,
// TODO: implement // guild_ban_remove: null = null,
// TODO: implement // guild_emojis_update: null = null,
// TODO: implement // guild_stickers_update: null = null,
// TODO: implement // guild_integrations_update: null = null,
// TODO: implement // guild_member_add: null = null,
// TODO: implement // guild_member_remove: null = null,
// TODO: implement // guild_member_update: null = null,
// TODO: implement // guild_members_chunk: null = null,
// TODO: implement // guild_role_create: null = null,
// TODO: implement // guild_role_update: null = null,
// TODO: implement // guild_role_delete: null = null,
// TODO: implement // guild_scheduled_event_create: null = null,
// TODO: implement // guild_scheduled_event_update: null = null,
// TODO: implement // guild_scheduled_event_delete: null = null,
// TODO: implement // guild_scheduled_event_user_add: null = null,
// TODO: implement // guild_scheduled_event_user_remove: null = null,
// TODO: implement // integration_create: null = null,
// TODO: implement // integration_update: null = null,
// TODO: implement // integration_delete: null = null,
// TODO: implement // interaction_create: null = null,
// TODO: implement // invite_create: null = null,
// TODO: implement // invite_delete: null = null,
message_create: *const fn (message: Discord.Message) void = undefined,
// TODO: implement // message_update: null = null,
// TODO: implement // message_delete: null = null,
// TODO: implement // message_delete_bulk: null = null,
// TODO: implement // message_reaction_add: null = null,
// TODO: implement // message_reaction_remove: null = null,
// TODO: implement // message_reaction_remove_all: null = null,
// TODO: implement // message_reaction_remove_emoji: null = null,
// TODO: implement // presence_update: null = null,
// TODO: implement // stage_instance_create: null = null,
// TODO: implement // stage_instance_update: null = null,
// TODO: implement // stage_instance_delete: null = null,
// TODO: implement // typing_start: null = null,
// TODO: implement // user_update: null = null,
// TODO: implement // voice_channel_effect_send: null = null,
// TODO: implement // voice_state_update: null = null,
// TODO: implement // voice_server_update: null = null,
// TODO: implement // webhooks_update: null = null,
// TODO: implement // entitlement_create: null = null,
// TODO: implement // entitlement_update: null = null,
// TODO: implement // entitlement_delete: null = null,
// TODO: implement // message_poll_vote_add: null = null,
// TODO: implement // message_poll_vote_remove: null = null,
// TODO: implement // ready: null = null,
// TODO: implement // resumed: null = null,
any: *const fn (data: []u8) void = undefined,
};
const FetchReq = struct {
allocator: mem.Allocator,
token: []const u8,
@ -197,9 +260,12 @@ resume_gateway_url: ?[]const u8 = null,
info: GatewayBotInfo,
session_id: ?[]const u8,
sequence: u64,
sequence: isize,
heart: Heart = .{ .heartbeatInterval = 45000, .ack = false, .lastBeat = 0 },
///
handler: GatewayDispatchEvent,
///useful for closing the conn
mutex: std.Thread.Mutex = .{},
@ -249,7 +315,11 @@ fn identify(self: *Self) !void {
}
// asks /gateway/bot initializes both the ws client and the http client
pub fn init(allocator: mem.Allocator, args: struct { token: []const u8, intents: Intents }) !Self {
pub fn init(allocator: mem.Allocator, args: struct {
token: []const u8,
intents: Intents,
run: GatewayDispatchEvent,
}) !Self {
var req = FetchReq.init(allocator, args.token);
defer req.deinit();
@ -275,6 +345,7 @@ pub fn init(allocator: mem.Allocator, args: struct { token: []const u8, intents:
.session_id = undefined,
.sequence = 0,
.info = parsed.value,
.handler = args.run,
};
}
@ -299,7 +370,7 @@ pub fn deinit(self: *Self) void {
}
// listens for messages
pub fn readMessage(self: *Self) !void {
pub fn readMessage(self: *Self, _: anytype) !void {
try self.client.readTimeout(0);
while (true) {
@ -310,52 +381,55 @@ pub fn readMessage(self: *Self) !void {
defer self.client.done(msg);
const DiscordData = struct {
s: ?u64, //well figure it out
const raw = try json.parseFromSlice(struct {
/// opcode for the payload
op: Opcode,
d: json.Value, // needs parsing
/// Event data
d: ?json.Value,
/// Sequence isize, used for resuming sessions and heartbeats
s: ?isize,
/// The event name for this payload
t: ?[]const u8,
};
const raw = try json.parseFromSlice(DiscordData, self.allocator, msg.data, .{});
}, self.allocator, msg.data, .{});
const payload = raw.value;
std.debug.print("received: {?s}\n", .{payload.t});
if (payload.op == Opcode.Dispatch) {
// maybe use mutex
self.setSequence(payload.s orelse 0);
}
switch (payload.op) {
Opcode.Dispatch => {},
Opcode.Dispatch => {
self.setSequence(payload.s orelse 0);
// maybe use threads and call it instead from there
if (payload.t) |name| try self.handleEvent(name, msg.data);
},
Opcode.Hello => {
const HelloPayload = struct { heartbeat_interval: u64, _trace: [][]const u8 };
const parsed = try json.parseFromValue(HelloPayload, self.allocator, payload.d, .{});
const helloPayload = parsed.value;
if (payload.d) |d| {
const HelloPayload = struct { heartbeat_interval: u64, _trace: [][]const u8 };
const parsed = try json.parseFromValue(HelloPayload, self.allocator, d, .{});
const helloPayload = parsed.value;
// PARSE NEW URL IN READY
// PARSE NEW URL IN READY
self.heart = Heart{
// TODO: fix bug
.heartbeatInterval = helloPayload.heartbeat_interval,
.ack = false,
.lastBeat = 0,
};
self.heart = Heart{
// TODO: fix bug
.heartbeatInterval = helloPayload.heartbeat_interval,
.ack = false,
.lastBeat = 0,
};
std.debug.print("starting heart beater. seconds:{d}...\n", .{self.heart.heartbeatInterval});
std.debug.print("starting heart beater. seconds:{d}...\n", .{self.heart.heartbeatInterval});
try self.heartbeat();
try self.heartbeat();
const thread = try std.Thread.spawn(.{}, Self.heartbeat_wait, .{self});
thread.detach();
const thread = try std.Thread.spawn(.{}, Self.heartbeat_wait, .{self});
thread.detach();
if (self.resumable()) {
try self.resume_();
return;
} else {
try self.identify();
if (self.resumable()) {
try self.resume_();
return;
} else {
try self.identify();
}
}
},
Opcode.HeartbeatACK => {
@ -379,13 +453,15 @@ pub fn readMessage(self: *Self) !void {
const WithSequence = struct {
token: []const u8,
session_id: []const u8,
seq: ?u64,
seq: ?isize,
};
const parsed = try json.parseFromValue(WithSequence, self.allocator, payload.d, .{});
const payload_new = parsed.value;
if (payload.d) |d| {
const parsed = try json.parseFromValue(WithSequence, self.allocator, d, .{});
const resume_payload = parsed.value;
self.setSequence(payload_new.seq orelse 0);
self.session_id = payload_new.session_id;
self.setSequence(resume_payload.seq orelse 0);
self.session_id = resume_payload.session_id;
}
},
Opcode.InvalidSession => {},
else => {
@ -464,13 +540,21 @@ pub fn send(self: *Self, data: anytype) !void {
try self.client.write(string.items);
}
pub inline fn getSequence(self: *Self) u64 {
pub inline fn getSequence(self: *Self) isize {
return self.sequence;
}
pub inline fn setSequence(self: *Self, new: u64) void {
pub inline fn setSequence(self: *Self, new: isize) void {
self.mutex.lock();
defer self.mutex.unlock();
self.sequence = new;
}
pub fn handleEvent(self: *Self, name: []const u8, payload: []const u8) !void {
if (std.ascii.eqlIgnoreCase(name, @tagName(.message_create))) {
const attempt = try std.json.parseFromSlice(Discord.Message, self.allocator, payload, .{});
defer attempt.deinit();
@call(.auto, self.handler.message_create, .{attempt.value});
} else {}
}

View File

@ -1,8 +1,13 @@
const Session = @import("discord.zig");
const Message = @import("types.zig").Message;
const std = @import("std");
const TOKEN = "Bot MTI5ODgzOTgzMDY3OTEzMDE4OA.GNojts.iyblGKK0xTWU57QCG5n3hr2Be1whyylTGr44P0";
fn message_create(data: Message) void {
std.debug.print("Event:{s}", .{data.content});
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer if (gpa.deinit() == .leak) {
@ -10,10 +15,14 @@ pub fn main() !void {
};
const alloc = gpa.allocator();
var handler = try Session.init(alloc, .{ .token = TOKEN, .intents = Session.Intents.fromRaw(513) });
var handler = try Session.init(alloc, .{
.token = TOKEN,
.intents = Session.Intents.fromRaw(513),
.run = Session.GatewayDispatchEvent{ .message_create = &message_create },
});
errdefer handler.deinit();
const t = try std.Thread.spawn(.{}, Session.readMessage, .{&handler});
const t = try std.Thread.spawn(.{}, Session.readMessage, .{ &handler, null });
defer t.join();
}

View File

@ -1,10 +1,10 @@
const std = @import("std");
const AutoHashMapUnmanaged = std.AutoHashMapUnmanaged;
pub fn Omit(comptime T: type, comptime field_names: [][]const u8) type {
pub fn Omit(comptime T: type, comptime field_names: anytype) type {
const info = @typeInfo(T);
switch (info) {
.Struct => |s| {
.@"struct" => |s| {
comptime var fields: []const std.builtin.Type.StructField = &[_]std.builtin.Type.StructField{};
outer: inline for (s.fields) |field| {
if (field.is_comptime) {
@ -12,14 +12,14 @@ pub fn Omit(comptime T: type, comptime field_names: [][]const u8) type {
}
inline for (field_names) |lookup| {
if (field == lookup) {
if (std.mem.eql(u8, field.name, lookup)) {
continue :outer;
}
}
fields = fields ++ field;
fields = fields ++ .{field};
}
const ti: std.builtin.Type = .{ .Struct = .{
const ti: std.builtin.Type = .{ .@"struct" = .{
.backing_integer = s.backing_integer,
.decls = &[_]std.builtin.Type.Declaration{},
.fields = fields,
@ -36,14 +36,14 @@ pub fn Omit(comptime T: type, comptime field_names: [][]const u8) type {
pub fn Partial(comptime T: type) type {
const info = @typeInfo(T);
switch (info) {
.Struct => |s| {
.@"struct" => |s| {
comptime var fields: []const std.builtin.Type.StructField = &[_]std.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);
}
const optional_type = switch (@typeInfo(field.type)) {
.Optional => field.type,
.optional => field.type,
else => ?field.type,
};
const default_value: optional_type = null;
@ -57,7 +57,7 @@ pub fn Partial(comptime T: type) type {
}};
fields = fields ++ optional_field;
}
const partial_type_info: std.builtin.Type = .{ .Struct = .{
const partial_type_info: std.builtin.Type = .{ .@"struct" = .{
.backing_integer = s.backing_integer,
.decls = &[_]std.builtin.Type.Declaration{},
.fields = fields,
@ -558,7 +558,7 @@ pub const ActivityTypes = enum(u4) {
};
/// https://discord.com/developers/docs/resources/channel#message-object-message-types
pub const MessageTypes = enum(u4) {
pub const MessageTypes = enum(u8) {
Default,
RecipientAdd,
RecipientRemove,
@ -2243,13 +2243,16 @@ pub const RoleTags = struct {
/// The id of the integration this role belongs to
integration_id: ?[]const u8,
/// Whether this is the guild's premium subscriber role
premium_subscriber: ?null,
/// Tags with type ?bool represent booleans. They will be present and set to null if they are "true", and will be not present if they are "false".
premium_subscriber: ?bool,
/// Id of this role's subscription sku and listing.
subscription_listing_id: ?[]const u8,
/// Whether this role is available for purchase.
available_for_purchase: ?null,
/// Tags with type ?bool represent booleans. They will be present and set to null if they are "true", and will be not present if they are "false".
available_for_purchase: ?bool,
/// Whether this is a guild's linked role
guild_connections: ?null,
/// Tags with type ?bool represent booleans. They will be present and set to null if they are "true", and will be not present if they are "false".
guild_connections: ?bool,
};
/// https://discord.com/developers/docs/resources/emoji#emoji-object-emoji-structure
@ -2632,7 +2635,8 @@ pub const MemberWithUser = struct {
user: User,
};
pub const MessageComponent = noreturn;
/// TODO: fix this
pub const MessageComponent = isize;
/// https://discord.com/developers/docs/resources/channel#message-object
pub const Message = struct {
@ -2749,8 +2753,8 @@ pub const Message = struct {
///
/// The message associated with the `message_reference`
/// Note: This field is only returned for messages with a `type` of `19` (REPLY). If the message is a reply but the `referenced_message` field is not present, the backend did not attempt to fetch the message that was being replied to, so its state is unknown. If the field exists but is null, the referenced message was deleted.,
///
referenced_message: ?Message,
/// TAKES A POINTER
referenced_message: ?*Message,
/// The message associated with the `message_reference`. This is a minimal subset of fields in a message (e.g. `author` is excluded.)
message_snapshots: []?MessageSnapshot,
/// sent if the message is sent as a result of an interaction
@ -3082,7 +3086,8 @@ pub const MessageInteractionMetadata = struct {
/// ID of the message that contained interactive component, present only on messages created from component interactions
interacted_message_id: ?[]const u8,
/// Metadata for the interaction that was used to open the modal, present only on modal submit interactions
triggering_interaction_metadata: ?MessageInteractionMetadata,
/// TAKES A POINTER
triggering_interaction_metadata: ?*MessageInteractionMetadata,
};
/// https://discord.com/developers/docs/resources/sticker#sticker-item-object-sticker-item-structure
@ -3260,7 +3265,7 @@ pub const InteractionData = struct {
/// The Ids and User objects
users: ?AutoHashMapUnmanaged([]const u8, User),
/// The Ids and partial Member objects
members: ?AutoHashMapUnmanaged([]const u8, Omit(InteractionMember, .{"user' | 'deaf' | 'mute"})),
members: ?AutoHashMapUnmanaged([]const u8, Omit(InteractionMember, .{ "user", "deaf", "mute" })),
/// The Ids and Role objects
roles: ?AutoHashMapUnmanaged([]const u8, Role),
/// The Ids and partial Channel objects