From 6689486660301235d178a3600f00a7e507b45f93 Mon Sep 17 00:00:00 2001 From: rainfall Date: Mon, 4 Nov 2024 13:06:16 -0500 Subject: [PATCH] fixed null ptr exception & added ready event --- README.md | 7 ++- build.zig | 4 +- src/discord.zig | 125 +++++++++++++++++++++++++--------------------- src/main.zig | 12 +++-- src/parser.zig | 33 ++++++++++++ src/raw_types.zig | 5 +- 6 files changed, 121 insertions(+), 65 deletions(-) create mode 100644 src/parser.zig diff --git a/README.md b/README.md index 430a6fc..49bedc6 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,12 @@ git clone https://github.com/jetzig-framework/zmpl.git ./lib/zmpl/ or simply run ./install.sh # features -* idk man +* scalable +* 100% API coverage, fully typed +* faster than any other Discord library +* language-agnostic +* implemented from scratch +* parses payloads using zlib ```zig // Sample code diff --git a/build.zig b/build.zig index 22fb2d8..825d17d 100644 --- a/build.zig +++ b/build.zig @@ -7,7 +7,7 @@ pub fn build(b: *std.Build) void { // these are boiler plate code until you know what you are doing // and you need to add additional options const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); + const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseFast }); // this is your own program const exe = b.addExecutable(.{ @@ -73,6 +73,8 @@ pub fn build(b: *std.Build) void { "lib/zlib/zutil.c", }; + //const mode = b.standardReleaseOptions(); + zlib_zig.addCSourceFiles(.{ .files = srcs, .flags = &.{"-std=c89"} }); zlib_zig.addIncludePath(b.path("lib/zlib/")); diff --git a/src/discord.zig b/src/discord.zig index a5fdaff..52d965f 100644 --- a/src/discord.zig +++ b/src/discord.zig @@ -13,6 +13,7 @@ const zlib = @import("zlib"); const zmpl = @import("zmpl"); const Discord = @import("raw_types.zig"); +const Parser = @import("parser.zig"); const debug = std.log.scoped(.@"discord.zig"); const Self = @This(); @@ -71,7 +72,7 @@ pub const GatewayDispatchEvent = struct { // 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, + 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, @@ -95,9 +96,9 @@ pub const GatewayDispatchEvent = struct { // TODO: implement // message_poll_vote_add: null = null, // TODO: implement // message_poll_vote_remove: null = null, - // TODO: implement // ready: null = null, + ready: ?*const fn (data: Discord.Ready) void = undefined, // TODO: implement // resumed: null = null, - any: *const fn (data: []u8) void = undefined, + any: ?*const fn (data: []u8) void = undefined, }; const FetchReq = struct { @@ -536,50 +537,81 @@ pub inline fn setSequence(self: *Self, new: isize) void { } pub fn handleEvent(self: *Self, name: []const u8, payload: []const u8) !void { - const attempt = try self.parseJson(payload); - if (std.ascii.eqlIgnoreCase(name, "ready")) { + var attempt = try self.parseJson(payload); + defer attempt.deinit(); + const obj = attempt.getT(.object, "d").?; self.resume_gateway_url = obj.getT(.string, "resume_gateway_url"); + self.logif("new gateway url: {s}", .{self.gatewayUrl()}); + + const application = obj.getT(.object, "application").?; + const user = Parser.parseUser(obj.getT(.object, "user").?); + + var ready = Discord.Ready{ + .v = @as(isize, @intCast(obj.getT(.integer, "v").?)), + .user = user, + .shard = null, + .session_id = obj.getT(.string, "session_id").?, + .guilds = &[0]Discord.UnavailableGuild{}, + .resume_gateway_url = obj.getT(.string, "resume_gateway_url").?, + .application = .{ + // todo + .name = null, + .description = null, + .rpc_origins = null, + .terms_of_service_url = null, + .privacy_policy_url = null, + .verify_key = null, + .primary_sku_id = null, + .slug = null, + .icon = null, + .bot_public = null, + .bot_require_code_grant = null, + .owner = null, + .team = null, + .guild_id = null, + .guild = null, + .cover_image = null, + .tags = null, + .install_params = null, + .integration_types_config = null, + .custom_install_url = null, + .role_connections_verification_url = null, + .approximate_guild_count = null, + .approximate_user_install_count = null, + .bot = null, + .redirect_uris = null, + .interactions_endpoint_url = null, + .flags = .{ .Embedded = true }, //@as(Discord.ApplicationFlags, @bitCast(@as(u25, @intCast(application.getT(.integer, "flags").?)))), + .id = application.getT(.string, "id").?, + }, + }; + + const shard = obj.getT(.array, "shard"); + + if (shard) |s| { + for (&ready.shard.?, s.items()) |*rs, ss| rs.* = switch (ss.*) { + .integer => |v| @as(isize, @intCast(v.value)), + else => unreachable, + }; + } + if (self.handler.ready) |event| @call(.auto, event, .{ready}); } if (std.ascii.eqlIgnoreCase(name, "message_create")) { + const attempt = try self.parseJson(payload); + const obj = attempt.getT(.object, "d").?; - const author_obj = obj.getT(.object, "author").?; const member_obj = obj.getT(.object, "member").?; - const avatar_decoration_data_obj = author_obj.getT(.object, "avatar_decoration_data"); - const avatar_decoration_data_member_obj = author_obj.getT(.object, "avatar_decoration_data"); + const avatar_decoration_data_member_obj = member_obj.getT(.object, "avatar_decoration_data"); const mentions_obj = obj.getT(.array, "mentions").?; var mentions = std.ArrayList(Discord.User).init(self.allocator); while (mentions_obj.iterator().next()) |m| { - const avatar_decoration_data_mention_obj = m.getT(.object, "avatar_decoration_data"); - try mentions.append(Discord.User{ - .id = m.getT(.string, "id").?, - .bot = m.getT(.boolean, "bot") orelse false, - .username = m.getT(.string, "username").?, - .accent_color = if (m.getT(.integer, "accent_color")) |ac| @as(isize, @intCast(ac)) else null, - // note: for selfbots this can be typed with an enu.?, - .flags = if (m.getT(.integer, "flags")) |fs| @as(isize, @intCast(fs)) else null, - // also for selfbot.?, - .email = m.getT(.string, "email"), - .avatar = m.getT(.string, "avatar"), - .locale = m.getT(.string, "locale"), - .system = m.getT(.boolean, "system"), - .banner = m.getT(.string, "banner"), - .verified = m.getT(.boolean, "verified"), - .global_name = m.getT(.string, "global_name"), - .mfa_enabled = m.getT(.boolean, "mfa_enabled"), - .public_flags = if (m.getT(.integer, "public_flags")) |pfs| @as(isize, @intCast(pfs)) else null, - .premium_type = if (m.getT(.integer, "premium_type")) |pfs| @as(Discord.PremiumTypes, @enumFromInt(pfs)) else null, - .discriminator = m.getT(.string, "discriminator").?, - .avatar_decoration_data = if (avatar_decoration_data_mention_obj) |addm| Discord.AvatarDecorationData{ - .asset = addm.getT(.string, "asset").?, - .sku_id = addm.getT(.string, "sku_id").?, - } else null, - }); + try mentions.append(Parser.parseUser(&m.object)); } const member = Discord.Member{ @@ -601,30 +633,7 @@ pub fn handleEvent(self: *Self, name: []const u8, payload: []const u8) !void { } else null, }; - const author = Discord.User{ - .id = author_obj.getT(.string, "id").?, - .bot = author_obj.getT(.boolean, "bot") orelse false, - .username = author_obj.getT(.string, "username").?, - .accent_color = if (author_obj.getT(.integer, "accent_color")) |ac| @as(isize, @intCast(ac)) else null, - // note: for selfbots this can be typed with an enu.?, - .flags = if (author_obj.getT(.integer, "flags")) |fs| @as(isize, @intCast(fs)) else null, - // also for selfbot.?, - .email = author_obj.getT(.string, "email"), - .avatar = author_obj.getT(.string, "avatar"), - .locale = author_obj.getT(.string, "locale"), - .system = author_obj.getT(.boolean, "system"), - .banner = author_obj.getT(.string, "banner"), - .verified = author_obj.getT(.boolean, "verified"), - .global_name = author_obj.getT(.string, "global_name"), - .mfa_enabled = author_obj.getT(.boolean, "mfa_enabled"), - .public_flags = if (author_obj.getT(.integer, "public_flags")) |pfs| @as(isize, @intCast(pfs)) else null, - .premium_type = if (author_obj.getT(.integer, "premium_type")) |pfs| @as(Discord.PremiumTypes, @enumFromInt(pfs)) else null, - .discriminator = author_obj.getT(.string, "discriminator").?, - .avatar_decoration_data = if (avatar_decoration_data_obj) |add| Discord.AvatarDecorationData{ - .asset = add.getT(.string, "asset").?, - .sku_id = add.getT(.string, "sku_id").?, - } else null, - }; + const author = Parser.parseUser(obj.getT(.object, "author").?); const m = Discord.Message{ // the id @@ -666,7 +675,7 @@ pub fn handleEvent(self: *Self, name: []const u8, payload: []const u8) !void { .call = null, }; - @call(.auto, self.handler.message_create, .{m}); + if (self.handler.message_create) |event| @call(.auto, event, .{m}); } else {} } diff --git a/src/main.zig b/src/main.zig index b0a76b2..d184a7d 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2,11 +2,14 @@ const Session = @import("discord.zig"); const Intents = @import("raw_types.zig").Intents; const Discord = @import("raw_types.zig"); const std = @import("std"); - const TOKEN = "Bot MTI5ODgzOTgzMDY3OTEzMDE4OA.GNojts.iyblGKK0xTWU57QCG5n3hr2Be1whyylTGr44P0"; +fn ready(payload: Discord.Ready) void { + std.debug.print("logged in as {s}\n", .{payload.user.username}); +} + fn message_create(message: Discord.Message) void { - std.debug.print("captured: {?s}\n", .{message.content}); + std.debug.print("captured: {?s} send by {s}\n", .{ message.content, message.author.username }); } pub fn main() !void { @@ -17,7 +20,10 @@ pub fn main() !void { var handler = try Session.init(allocator, .{ .token = TOKEN, .intents = Intents.fromRaw(37379), - .run = Session.GatewayDispatchEvent{ .message_create = &message_create }, + .run = Session.GatewayDispatchEvent{ + .message_create = &message_create, + .ready = &ready, + }, .log = .yes, }); errdefer handler.deinit(); diff --git a/src/parser.zig b/src/parser.zig new file mode 100644 index 0000000..de7f690 --- /dev/null +++ b/src/parser.zig @@ -0,0 +1,33 @@ +const zmpl = @import("zmpl"); +const Discord = @import("raw_types.zig"); + +pub fn parseUser(obj: *zmpl.Data.Object) Discord.User { + const avatar_decoration_data_obj = obj.getT(.object, "avatar_decoration_data"); + const user = Discord.User{ + .clan = null, + .id = obj.getT(.string, "id").?, + .bot = obj.getT(.boolean, "bot") orelse false, + .username = obj.getT(.string, "username").?, + .accent_color = if (obj.getT(.integer, "accent_color")) |ac| @as(isize, @intCast(ac)) else null, + // note: for selfbots this can be typed with an enu.?, + .flags = if (obj.getT(.integer, "flags")) |fs| @as(isize, @intCast(fs)) else null, + // also for selfbot.?, + .email = obj.getT(.string, "email"), + .avatar = obj.getT(.string, "avatar"), + .locale = obj.getT(.string, "locale"), + .system = obj.getT(.boolean, "system"), + .banner = obj.getT(.string, "banner"), + .verified = obj.getT(.boolean, "verified"), + .global_name = obj.getT(.string, "global_name"), + .mfa_enabled = obj.getT(.boolean, "mfa_enabled"), + .public_flags = if (obj.getT(.integer, "public_flags")) |pfs| @as(isize, @intCast(pfs)) else null, + .premium_type = if (obj.getT(.integer, "premium_type")) |pfs| @as(Discord.PremiumTypes, @enumFromInt(pfs)) else null, + .discriminator = obj.getT(.string, "discriminator").?, + .avatar_decoration_data = if (avatar_decoration_data_obj) |add| Discord.AvatarDecorationData{ + .asset = add.getT(.string, "asset").?, + .sku_id = add.getT(.string, "sku_id").?, + } else null, + }; + + return user; +} diff --git a/src/raw_types.zig b/src/raw_types.zig index bad2a8f..1e885fa 100644 --- a/src/raw_types.zig +++ b/src/raw_types.zig @@ -1345,6 +1345,7 @@ pub const User = struct { banner: ?[]const u8, /// data for the user's avatar decoration avatar_decoration_data: ?AvatarDecorationData, + clan: ?[]const u8, }; /// https://discord.com/developers/docs/topics/oauth2#shared-resources-oauth2-scopes @@ -4336,10 +4337,10 @@ pub const Ready = struct { approximate_guild_count: ?isize, approximate_user_install_count: ?isize, bot: ?Partial(User), - redirect_uris: []?[]const u8, + redirect_uris: ?[][]const u8, interactions_endpoint_url: ?[]const u8, - flags: ?ApplicationFlags, + flags: ApplicationFlags, id: []const u8, }, };