summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Uko Kokņevičs2024-07-20 17:22:25 +0300
committerGravatar Uko Kokņevičs2024-07-20 17:22:25 +0300
commitc70ffd095a6de5cd5b872796a0d82a8c5afc1511 (patch)
tree56183274b05a294e357bad4d06b523472a1c4a4a /src
downloadukkobot-c70ffd095a6de5cd5b872796a0d82a8c5afc1511.tar.gz
ukkobot-c70ffd095a6de5cd5b872796a0d82a8c5afc1511.tar.xz
ukkobot-c70ffd095a6de5cd5b872796a0d82a8c5afc1511.zip
Initial commit
Diffstat (limited to 'src')
-rw-r--r--src/Bot.zig239
-rw-r--r--src/Config.zig83
-rw-r--r--src/json.zig108
-rw-r--r--src/main.zig353
-rw-r--r--src/textutils.zig16
-rw-r--r--src/types.zig99
-rw-r--r--src/types/Animation.zig11
-rw-r--r--src/types/Audio.zig11
-rw-r--r--src/types/BotName.zig1
-rw-r--r--src/types/BusinessConnection.zig8
-rw-r--r--src/types/BusinessMessagesDeleted.zig5
-rw-r--r--src/types/CallbackGame.zig1
-rw-r--r--src/types/CallbackQuery.zig10
-rw-r--r--src/types/Chat.zig14
-rw-r--r--src/types/ChatBackground.zig3
-rw-r--r--src/types/ChatBoost.zig6
-rw-r--r--src/types/ChatBoostAdded.zig1
-rw-r--r--src/types/ChatBoostRemoved.zig7
-rw-r--r--src/types/ChatBoostUpdated.zig5
-rw-r--r--src/types/ChatInviteLink.zig11
-rw-r--r--src/types/ChatJoinRequest.zig10
-rw-r--r--src/types/ChatMemberUpdated.zig13
-rw-r--r--src/types/ChatShared.zig7
-rw-r--r--src/types/ChosenInlineResult.zig8
-rw-r--r--src/types/Contact.zig5
-rw-r--r--src/types/Dice.zig2
-rw-r--r--src/types/Document.zig8
-rw-r--r--src/types/EditMessageTextParams.zig16
-rw-r--r--src/types/EncryptedCredentials.zig3
-rw-r--r--src/types/EncryptedPassportElement.zig30
-rw-r--r--src/types/ExternalReplyInfo.zig47
-rw-r--r--src/types/File.zig4
-rw-r--r--src/types/ForumTopicClosed.zig1
-rw-r--r--src/types/ForumTopicCreated.zig3
-rw-r--r--src/types/ForumTopicEdited.zig2
-rw-r--r--src/types/ForumTopicReopened.zig1
-rw-r--r--src/types/Game.zig10
-rw-r--r--src/types/GeneralForumTopicHidden.zig1
-rw-r--r--src/types/GeneralForumTopicUnhidden.zig1
-rw-r--r--src/types/GetMyNameParams.zig1
-rw-r--r--src/types/GetUpdatesParams.zig4
-rw-r--r--src/types/Giveaway.zig10
-rw-r--r--src/types/GiveawayCompleted.zig5
-rw-r--r--src/types/GiveawayCreated.zig1
-rw-r--r--src/types/GiveawayWinners.zig14
-rw-r--r--src/types/InlineKeyboardButton.zig15
-rw-r--r--src/types/InlineKeyboardMarkup.zig3
-rw-r--r--src/types/InlineQuery.zig17
-rw-r--r--src/types/Invoice.zig5
-rw-r--r--src/types/LinkPreviewOptions.zig5
-rw-r--r--src/types/Location.zig6
-rw-r--r--src/types/LoginUrl.zig4
-rw-r--r--src/types/MaskPosition.zig11
-rw-r--r--src/types/Message.zig139
-rw-r--r--src/types/MessageAutoDeleteTimerChanged.zig1
-rw-r--r--src/types/MessageEntity.zig66
-rw-r--r--src/types/MessageReactionCountUpdated.zig7
-rw-r--r--src/types/MessageReactionUpdated.zig11
-rw-r--r--src/types/OrderInfo.zig6
-rw-r--r--src/types/PaidMediaInfo.zig4
-rw-r--r--src/types/PassportData.zig5
-rw-r--r--src/types/PassportFile.zig4
-rw-r--r--src/types/PhotoSize.zig5
-rw-r--r--src/types/Poll.zig22
-rw-r--r--src/types/PollAnswer.zig7
-rw-r--r--src/types/PollOption.zig5
-rw-r--r--src/types/PreCheckoutQuery.zig10
-rw-r--r--src/types/ProximityAlertTriggered.zig5
-rw-r--r--src/types/ReactionCount.zig4
-rw-r--r--src/types/RefundedPayment.zig5
-rw-r--r--src/types/ReplyParameters.zig9
-rw-r--r--src/types/ResponseParameters.zig2
-rw-r--r--src/types/SendMessageParams.zig18
-rw-r--r--src/types/SetMyNameParams.zig2
-rw-r--r--src/types/SharedUser.zig7
-rw-r--r--src/types/ShippingAddress.zig6
-rw-r--r--src/types/ShippingQuery.zig7
-rw-r--r--src/types/Sticker.zig25
-rw-r--r--src/types/Story.zig4
-rw-r--r--src/types/SuccessfulPayment.zig9
-rw-r--r--src/types/SwitchInlineQueryChosenChat.zig5
-rw-r--r--src/types/TextQuote.zig6
-rw-r--r--src/types/Update.zig41
-rw-r--r--src/types/User.zig32
-rw-r--r--src/types/UsersShared.zig4
-rw-r--r--src/types/Venue.zig9
-rw-r--r--src/types/Video.zig11
-rw-r--r--src/types/VideoChatEnded.zig1
-rw-r--r--src/types/VideoChatParticipantsInvited.zig3
-rw-r--r--src/types/VideoChatScheduled.zig1
-rw-r--r--src/types/VideoChatStarted.zig1
-rw-r--r--src/types/VideoNote.zig8
-rw-r--r--src/types/Voice.zig5
-rw-r--r--src/types/WebAppData.zig2
-rw-r--r--src/types/WebAppInfo.zig1
-rw-r--r--src/types/WriteAccessAllowed.zig3
-rw-r--r--src/types/background_fill.zig17
-rw-r--r--src/types/background_type.zig30
-rw-r--r--src/types/chat_boost_source.zig20
-rw-r--r--src/types/chat_member.zig63
-rw-r--r--src/types/message_origin.zig29
-rw-r--r--src/types/paid_media.zig21
-rw-r--r--src/types/parse_mode.zig5
-rw-r--r--src/types/reaction_type.zig13
104 files changed, 1991 insertions, 0 deletions
diff --git a/src/Bot.zig b/src/Bot.zig
new file mode 100644
index 0000000..d40a4a0
--- /dev/null
+++ b/src/Bot.zig
@@ -0,0 +1,239 @@
1const types = @import("types.zig");
2const std = @import("std");
3
4const Allocator = std.mem.Allocator;
5const ArrayList = std.ArrayList;
6const Bot = @This();
7const Config = @import("Config.zig");
8const HttpClient = std.http.Client;
9const HttpMethod = std.http.Method;
10const Parsed = std.json.Parsed;
11const Uri = std.Uri;
12
13allocator: Allocator,
14http_client: HttpClient,
15config: Config,
16base_uri: Uri = Uri.parse("https://api.telegram.org/") catch unreachable,
17uri_path_data: ArrayList(u8),
18poweron: bool = true,
19server_header_buffer: [4096]u8 = undefined,
20username: ?[]const u8 = null,
21
22pub fn init(allocator: Allocator, config: Config) !Bot {
23 var uri_path_data = try ArrayList(u8).initCapacity(allocator, 5 + config.bot_token.len);
24 errdefer uri_path_data.deinit();
25
26 uri_path_data.appendSliceAssumeCapacity("/bot");
27 uri_path_data.appendSliceAssumeCapacity(config.bot_token);
28 uri_path_data.appendAssumeCapacity('/');
29
30 return .{
31 .allocator = allocator,
32 .http_client = .{
33 .allocator = allocator,
34 },
35 .config = config,
36 .uri_path_data = uri_path_data,
37 };
38}
39
40pub fn deinit(self: *Bot) void {
41 self.http_client.deinit();
42 self.uri_path_data.deinit();
43 if (self.username) |username| self.allocator.free(username);
44
45 self.* = undefined;
46}
47
48pub inline fn editMessageText(self: *Bot, args: types.EditMessageTextParams) !Parsed(types.Message) {
49 return self.post(types.Message, "editMessageText", args);
50}
51
52pub inline fn editMessageText_(self: *Bot, args: types.EditMessageTextParams) !void {
53 (try self.editMessageText(args)).deinit();
54}
55
56pub inline fn getMe(self: *Bot) !Parsed(types.User) {
57 return self.get(types.User, "getMe", null);
58}
59
60pub inline fn getMyName(self: *Bot, args: types.GetMyNameParams) !Parsed(types.BotName) {
61 return self.get(types.BotName, "getMyName", args);
62}
63
64pub inline fn getUpdates(self: *Bot, args: types.GetUpdatesParams) !Parsed([]types.Update) {
65 return self.get([]types.Update, "getUpdates", args);
66}
67
68pub inline fn getUsername(self: *Bot) ![]const u8 {
69 if (self.username) |username| return username;
70 const user = try self.getMe();
71 defer user.deinit();
72 self.username = user.value.username;
73 return self.username.?;
74}
75
76pub inline fn sendMessage(self: *Bot, args: types.SendMessageParams) !Parsed(types.Message) {
77 return self.post(types.Message, "sendMessage", args);
78}
79
80pub inline fn sendMessage_(self: *Bot, args: types.SendMessageParams) !void {
81 (try self.sendMessage(args)).deinit();
82}
83
84pub inline fn setMyName(self: *Bot, args: types.SetMyNameParams) !void {
85 if (args.name) |new_name| {
86 // Check if the current name isn't the same as what we want to change to
87 const curr_name = try self.getMyName(.{ .language_code = args.language_code });
88 defer curr_name.deinit();
89
90 if (std.mem.eql(u8, curr_name.value.name, new_name)) {
91 return;
92 }
93 }
94
95 const res = try self.post(bool, "setMyName", args);
96 defer res.deinit();
97 if (!res.value) {
98 return error.FailedToSetName;
99 }
100}
101
102fn Wrapper(comptime T: type) type {
103 return struct {
104 ok: bool,
105 description: ?[]const u8 = null,
106 result: ?T = null,
107 error_code: ?i64 = null,
108 parameters: ?types.ResponseParameters = null,
109 };
110}
111
112fn call(
113 self: *Bot,
114 comptime T: type,
115 comptime method: HttpMethod,
116 uri: Uri,
117 data: ?[]const u8,
118) !Parsed(T) {
119 var request = try self.http_client.open(method, uri, .{
120 .server_header_buffer = &self.server_header_buffer,
121 });
122 defer request.deinit();
123
124 if (data) |s| {
125 request.headers.content_type = .{ .override = "application/json" };
126 request.transfer_encoding = .{ .content_length = s.len };
127 }
128 try request.send();
129
130 if (data) |s| {
131 try request.writeAll(s);
132 }
133 try request.finish();
134
135 try request.wait();
136
137 var reader = std.json.reader(self.allocator, request.reader());
138 defer reader.deinit();
139
140 const result = try std.json.parseFromTokenSource(
141 Wrapper(T),
142 self.allocator,
143 &reader,
144 .{
145 .ignore_unknown_fields = true,
146 .allocate = .alloc_always,
147 },
148 );
149 errdefer result.deinit();
150
151 if (!result.value.ok or result.value.result == null) {
152 std.log.err("Request failed: {any}", .{result.value});
153 return error.RequestFailed;
154 }
155
156 return .{
157 .arena = result.arena,
158 .value = result.value.result.?,
159 };
160}
161
162inline fn isNull(value: anytype) bool {
163 return switch (@typeInfo(@TypeOf(value))) {
164 .Null => true,
165 .Optional => value == null,
166 else => false,
167 };
168}
169
170fn intoQueryString(allocator: Allocator, data: anytype) !?[]u8 {
171 return switch (@typeInfo(@TypeOf(data))) {
172 .Null => null,
173 .Optional => if (data) |d| intoQueryString(allocator, d) else null,
174 .Struct => |s| {
175 var sb = ArrayList(u8).init(allocator);
176 defer sb.deinit();
177
178 var counter: usize = 0;
179
180 inline for (s.fields) |field| {
181 if (!isNull(@field(data, field.name))) {
182 counter += 1;
183
184 try sb.ensureUnusedCapacity(field.name.len + 2);
185 if (counter != 1) {
186 sb.appendAssumeCapacity('&');
187 }
188
189 sb.appendSliceAssumeCapacity(field.name);
190 sb.appendAssumeCapacity('=');
191
192 const value = try std.json.stringifyAlloc(
193 allocator,
194 @field(data, field.name),
195 .{ .emit_null_optional_fields = false },
196 );
197 defer allocator.free(value);
198 try sb.appendSlice(value);
199 }
200 }
201
202 return try sb.toOwnedSlice();
203 },
204 else => @compileError(@typeName(@TypeOf(data)) ++ " not supported"),
205 };
206}
207
208inline fn get(self: *Bot, Out: type, comptime path: []const u8, args: anytype) !Parsed(Out) {
209 const path_len = self.uri_path_data.items.len;
210 defer self.uri_path_data.shrinkRetainingCapacity(path_len);
211
212 try self.uri_path_data.appendSlice(path);
213
214 var uri = self.base_uri;
215 uri.path = .{ .raw = self.uri_path_data.items };
216
217 const query = try intoQueryString(self.allocator, args);
218 defer if (query) |q| self.allocator.free(q);
219 if (query) |q| uri.query = .{ .raw = q };
220
221 std.log.debug("GET {}", .{uri});
222 return self.call(Out, .GET, uri, null);
223}
224
225inline fn post(self: *Bot, Out: type, comptime path: []const u8, args: anytype) !Parsed(Out) {
226 const str_data = try std.json.stringifyAlloc(self.allocator, args, .{ .emit_null_optional_fields = false });
227 defer self.allocator.free(str_data);
228
229 const path_len = self.uri_path_data.items.len;
230 defer self.uri_path_data.shrinkRetainingCapacity(path_len);
231
232 try self.uri_path_data.appendSlice(path);
233
234 var uri = self.base_uri;
235 uri.path = .{ .raw = self.uri_path_data.items };
236
237 std.log.debug("POST {}", .{uri});
238 return self.call(Out, .POST, uri, str_data);
239}
diff --git a/src/Config.zig b/src/Config.zig
new file mode 100644
index 0000000..4deebbc
--- /dev/null
+++ b/src/Config.zig
@@ -0,0 +1,83 @@
1const std = @import("std");
2
3const Allocator = std.mem.Allocator;
4const ArenaAllocator = std.heap.ArenaAllocator;
5const Config = @This();
6
7pub const Wrapper = struct {
8 arena: ArenaAllocator,
9 config: Config,
10
11 pub fn deinit(self: Wrapper) void {
12 self.arena.deinit();
13 }
14
15 pub fn merge(self: *Wrapper, filename: []const u8) !void {
16 const file = try std.fs.cwd().openFile(filename, .{});
17 defer file.close();
18
19 const allocator = self.arena.allocator();
20
21 var reader = std.json.reader(allocator, file.reader());
22 defer reader.deinit();
23
24 const new_config = try std.json.parseFromTokenSourceLeaky(
25 Nullable,
26 allocator,
27 &reader,
28 .{
29 .duplicate_field_behavior = .use_last,
30 .ignore_unknown_fields = true,
31 .allocate = .alloc_always,
32 },
33 );
34
35 inline for (std.meta.fields(Config)) |field| {
36 if (@field(new_config, field.name)) |value| {
37 @field(self.config, field.name) = value;
38 }
39 }
40 }
41};
42
43bot_token: []const u8,
44dev_group: i64,
45owner: i64,
46
47pub fn load(parent_allocator: Allocator, filename: []const u8) !Wrapper {
48 const file = try std.fs.cwd().openFile(filename, .{});
49 defer file.close();
50
51 var arena = ArenaAllocator.init(parent_allocator);
52 errdefer arena.deinit();
53 const allocator = arena.allocator();
54
55 var reader = std.json.reader(allocator, file.reader());
56 defer reader.deinit();
57
58 const config = try std.json.parseFromTokenSourceLeaky(
59 Config,
60 allocator,
61 &reader,
62 .{
63 .duplicate_field_behavior = .use_last,
64 .ignore_unknown_fields = true,
65 .allocate = .alloc_always,
66 },
67 );
68
69 return .{ .arena = arena, .config = config };
70}
71
72const Nullable = blk: {
73 const template = @typeInfo(Config);
74 var fields: [template.Struct.fields.len]std.builtin.Type.StructField = undefined;
75 for (template.Struct.fields, 0..) |template_field, field_idx| {
76 fields[field_idx] = template_field;
77 fields[field_idx].type = ?template_field.type;
78 }
79 var new = template;
80 new.Struct.fields = &fields;
81 new.Struct.decls = &.{};
82 break :blk @Type(new);
83};
diff --git a/src/json.zig b/src/json.zig
new file mode 100644
index 0000000..9252344
--- /dev/null
+++ b/src/json.zig
@@ -0,0 +1,108 @@
1// TODO: ALSO IMPLEMENT jsonStringify !!!
2
3const std = @import("std");
4
5const Allocator = std.mem.Allocator;
6const ParseError = std.json.ParseError;
7const ParseFromValueError = std.json.ParseFromValueError;
8const ParseOptions = std.json.ParseOptions;
9const Value = std.json.Value;
10
11pub fn JsonParse(T: type) type {
12 const f = struct {
13 pub fn f(allocator: Allocator, source: anytype, options: ParseOptions) ParseError(@TypeOf(source.*))!T {
14 _ = allocator;
15 _ = options;
16 unreachable;
17 }
18 }.f;
19 return @TypeOf(f);
20}
21
22pub fn makeJsonParse(T: type) JsonParse(T) {
23 comptime if (!std.meta.hasFn(T, "jsonParseFromValue")) {
24 @compileError("Did you forget `pub const jsonParseFromValue = json.makeJsonParseFromValue(" ++ @typeName(T) ++ ") ?");
25 };
26
27 return struct {
28 pub fn jsonParse(allocator: Allocator, source: anytype, options: ParseOptions) ParseError(@TypeOf(source.*))!T {
29 const value = try std.json.innerParse(Value, allocator, source, options);
30 return try T.jsonParseFromValue(allocator, value, options);
31 }
32 }.jsonParse;
33}
34
35pub fn JsonParseFromValue(T: type) type {
36 const f = struct {
37 pub fn f(allocator: Allocator, source: Value, options: ParseOptions) ParseFromValueError!T {
38 _ = allocator;
39 _ = source;
40 _ = options;
41 unreachable;
42 }
43 }.f;
44 return @TypeOf(f);
45}
46
47pub fn makeJsonParseFromValue(T: type) JsonParseFromValue(T) {
48 return makeJsonParseFromValueWithTag(T, "type");
49}
50
51pub fn makeJsonParseFromValueWithTag(T: type, comptime tag: [:0]const u8) JsonParseFromValue(T) {
52 const union_info = switch (@typeInfo(T)) {
53 .Union => |info| blk: {
54 if (info.tag_type == null) {
55 @compileError("Only tagged unions supported, got '" ++ @typeName(T) ++ "'");
56 }
57 break :blk info;
58 },
59 else => @compileError("Only unions supported, got '" ++ @typeName(T) ++ "'"),
60 };
61
62 const TagType = union_info.tag_type.?;
63
64 const Base = blk: {
65 const info = std.builtin.Type{ .Struct = .{
66 .layout = .auto,
67 .fields = &.{.{
68 .name = tag,
69 .type = TagType,
70 .default_value = null,
71 .is_comptime = false,
72 .alignment = 0,
73 }},
74 .decls = &.{},
75 .is_tuple = false,
76 } };
77 break :blk @Type(info);
78 };
79
80 return struct {
81 pub fn jsonParseFromValue(
82 allocator: Allocator,
83 source: Value,
84 options: ParseOptions,
85 ) ParseFromValueError!T {
86 const new_options = blk: {
87 var opt = options;
88 opt.ignore_unknown_fields = true;
89 break :blk opt;
90 };
91
92 const base = try std.json.innerParseFromValue(Base, allocator, source, new_options);
93 const typeName = @tagName(@field(base, tag));
94 // TODO: Upgrade to StaticStringMap
95 inline for (union_info.fields) |field_info| {
96 if (std.mem.eql(u8, typeName, field_info.name)) {
97 return @unionInit(
98 T,
99 field_info.name,
100 try std.json.innerParseFromValue(field_info.type, allocator, source, new_options),
101 );
102 }
103 }
104
105 unreachable;
106 }
107 }.jsonParseFromValue;
108}
diff --git a/src/main.zig b/src/main.zig
new file mode 100644
index 0000000..0c524a7
--- /dev/null
+++ b/src/main.zig
@@ -0,0 +1,353 @@
1const types = @import("types.zig");
2const std = @import("std");
3
4const Allocator = std.mem.Allocator;
5const ArrayList = std.ArrayList;
6const Bot = @import("Bot.zig");
7const Config = @import("Config.zig");
8const GPA = std.heap.GeneralPurposeAllocator(.{});
9
10pub fn main() !void {
11 defer std.log.info("We're done", .{});
12
13 var gpa = GPA{};
14 const allocator = gpa.allocator();
15 defer _ = gpa.deinit();
16
17 // Load config
18 var config = try Config.load(allocator, "config.default.json");
19 defer config.deinit();
20 try config.merge("config.json");
21
22 var bot = try Bot.init(allocator, config.config);
23 defer bot.deinit();
24
25 // TODO: Catch fatal errors, report them
26 try wrappedMain(&bot);
27}
28
29fn loadConfig(allocator: Allocator, filename: []const u8) !std.json.Parsed(Config) {
30 const file = try std.fs.cwd().openFile(filename, .{});
31 defer file.close();
32
33 var reader = std.json.reader(allocator, file.reader());
34 defer reader.deinit();
35
36 return try std.json.parseFromTokenSource(
37 Config,
38 allocator,
39 &reader,
40 .{
41 .duplicate_field_behavior = .use_last,
42 .ignore_unknown_fields = true,
43 .allocate = .alloc_always,
44 },
45 );
46}
47
48fn wrappedMain(bot: *Bot) !void {
49 try bot.sendMessage_(.{
50 .chat_id = bot.config.dev_group,
51 .text = "Initializing...",
52 });
53
54 try bot.setMyName(.{ .name = "Ukko's bot" });
55
56 var gup = types.GetUpdatesParams{ .timeout = 60 };
57 while (bot.poweron) {
58 // TODO: Catch major errors, report them (and crash after 5 of them or so)
59 const updates = try bot.getUpdates(gup);
60 defer updates.deinit();
61 for (updates.value) |update| {
62 defer gup.offset = update.update_id + 1;
63
64 if (update.message) |message| {
65 // TODO: Catch minor errors, report them
66 try onMessage(bot, message);
67 }
68 }
69 }
70
71 // one last getUpdates to make sure offset is saved
72 gup.timeout = 0;
73 gup.limit = 1;
74 (try bot.getUpdates(gup)).deinit();
75
76 try bot.sendMessage_(.{
77 .chat_id = bot.config.dev_group,
78 .text = "Shutting down...",
79 });
80}
81
82fn onMessage(bot: *Bot, msg: types.Message) !void {
83 if (msg.text) |text| {
84 try onTextMessage(bot, msg, text);
85 }
86
87 if (msg.new_chat_members) |new_chat_members| {
88 for (new_chat_members) |new_chat_member| {
89 try onNewMember(bot, msg, new_chat_member);
90 }
91 }
92}
93
94fn onNewMember(bot: *Bot, msg: types.Message, new_member: types.User) !void {
95 var sb = ArrayList(u8).init(bot.allocator);
96 defer sb.deinit();
97
98 const w = sb.writer();
99
100 try w.writeAll("Hello there, ");
101 try new_member.writeFormattedName(w);
102 try w.writeAll("! Be on your bestest behaviour now!!");
103
104 try bot.sendMessage_(.{
105 .chat_id = msg.chat.id,
106 .text = sb.items,
107 .parse_mode = .html,
108 .reply_parameters = .{
109 .allow_sending_without_reply = true,
110 .message_id = msg.message_id,
111 .chat_id = msg.chat.id,
112 },
113 });
114}
115
116fn isBadText(text: []const u8) bool {
117 _ = text;
118 return false;
119}
120
121fn onTextMessage(bot: *Bot, msg: types.Message, text: []const u8) !void {
122 if (isBadText(text)) {
123 // TODO: Delete message, mute & warn user
124 // 0 current warns: 5 minute mute, +1 warn
125 // 1 current warn : 10 minute mute, +1 warn
126 // 2 current warns: 30 minute mute, +1 warn
127 // 3 current warns: 1 hour mute, +1 warn
128 // 4 current warns: 1 day mute, +1 warn
129 // 5 current warns: Ban
130 //
131 // warn gets removed after a month of no warns
132 //
133 // Lines to say in response:
134 // Your head will be my new trophy!
135 // Your cursed bloodline ends here!
136 // This is the end of you, s'wit!
137 // Your life's end is approaching.
138 // Surrender your life to me and I will end your pain!
139 // Your pain is nearing an end.
140 // May our Lords be merciful!
141 // You have sealed your fate!
142 // You cannot escape the righteous!
143 // You will pay with your blood!
144 // You will die.
145 // There is no escape.
146 // Die, fetcher.
147 // I shall enjoy watching you take your last breath.
148 // You'll soon be nothing more than a bad memory!
149 // You will die in disgrace.
150 // I'll see you dead.
151 // One of us will die here and it won't be me.
152 // You don't deserve to live.
153 // Surrender now and I might let you live!
154 // I will bathe in your blood.
155 // Your bones will be my dinner.
156 // So small and tasty. I will enjoy eating you.
157 return;
158 }
159
160 if (msg.entities) |entities| {
161 for (entities) |entity| {
162 if (entity.type == .bot_command and entity.offset == 0) {
163 const cmd = try entity.extract(text);
164 try onTextCommand(bot, msg, text, cmd);
165 }
166 }
167 }
168
169 if (std.mem.eql(u8, text, ":3")) {
170 try bot.sendMessage_(.{
171 .chat_id = msg.chat.id,
172 .text = ">:3",
173 .reply_parameters = .{
174 .message_id = msg.message_id,
175 .chat_id = msg.chat.id,
176 },
177 });
178 } else if (std.mem.eql(u8, text, ">:3")) {
179 try bot.sendMessage_(.{
180 .chat_id = msg.chat.id,
181 .text = "<b>&gt;:3</b>",
182 .parse_mode = .html,
183 .reply_parameters = .{
184 .message_id = msg.message_id,
185 .chat_id = msg.chat.id,
186 },
187 });
188 } else if (std.ascii.startsWithIgnoreCase(text, "big ")) {
189 var output = try bot.allocator.alloc(u8, text.len + 3);
190 defer bot.allocator.free(output);
191
192 std.mem.copyForwards(u8, output, "<b>");
193 _ = std.ascii.upperString(output[3..], text[4..]);
194 std.mem.copyForwards(u8, output[output.len - 4 ..], "</b>");
195
196 try bot.sendMessage_(.{
197 .chat_id = msg.chat.id,
198 .text = output,
199 .parse_mode = .html,
200 .reply_parameters = .{
201 .message_id = msg.message_id,
202 .chat_id = msg.chat.id,
203 },
204 });
205 } else if (std.mem.eql(u8, text, "H")) {
206 try bot.sendMessage_(.{
207 .chat_id = msg.chat.id,
208 .text = "<code>Randomly selected reminder that h &gt; H.</code>",
209 .parse_mode = .html,
210 .reply_parameters = .{
211 .message_id = msg.message_id,
212 .chat_id = msg.chat.id,
213 },
214 });
215 } else if (std.ascii.startsWithIgnoreCase(text, "say ")) {
216 try bot.sendMessage_(.{
217 .chat_id = msg.chat.id,
218 .text = text[4..],
219 .reply_parameters = .{
220 .message_id = msg.message_id,
221 .chat_id = msg.chat.id,
222 },
223 });
224 } else if (std.ascii.eqlIgnoreCase(text, "uwu")) {
225 try bot.sendMessage_(.{
226 .chat_id = msg.chat.id,
227 .text = "OwO",
228 .reply_parameters = .{
229 .message_id = msg.message_id,
230 .chat_id = msg.chat.id,
231 },
232 });
233 } else if (std.ascii.eqlIgnoreCase(text, "waow")) {
234 const reply_to = if (msg.reply_to_message) |r|
235 r.message_id
236 else
237 msg.message_id;
238
239 try bot.sendMessage_(.{
240 .chat_id = msg.chat.id,
241 .text = "BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED",
242 .reply_parameters = .{
243 .message_id = reply_to,
244 .chat_id = msg.chat.id,
245 },
246 });
247 } else if (std.ascii.eqlIgnoreCase(text, "what")) {
248 var sb = try ArrayList(u8).initCapacity(bot.allocator, 9);
249 defer sb.deinit();
250
251 if (text[0] == 'w') {
252 sb.appendSliceAssumeCapacity("g");
253 } else {
254 sb.appendSliceAssumeCapacity("G");
255 }
256 if (text[1] == 'h') {
257 sb.appendSliceAssumeCapacity("ood ");
258 } else {
259 sb.appendSliceAssumeCapacity("OOD ");
260 }
261 if (text[2] == 'a') {
262 sb.appendSliceAssumeCapacity("gir");
263 } else {
264 sb.appendSliceAssumeCapacity("GIR");
265 }
266 if (text[3] == 't') {
267 sb.appendSliceAssumeCapacity("l");
268 } else {
269 sb.appendSliceAssumeCapacity("L");
270 }
271
272 try bot.sendMessage_(.{
273 .chat_id = msg.chat.id,
274 .text = sb.items,
275 .reply_parameters = .{
276 .message_id = msg.message_id,
277 .chat_id = msg.chat.id,
278 },
279 });
280 }
281}
282
283fn onTextCommand(bot: *Bot, msg: types.Message, text: []const u8, cmd: []const u8) !void {
284 _ = text;
285
286 const simple_cmd = if (std.mem.indexOfScalar(u8, cmd, '@')) |idx| blk: {
287 const cmd_username = cmd[idx + 1 ..];
288 if (!std.mem.eql(u8, cmd_username, try bot.getUsername())) {
289 return;
290 }
291 break :blk cmd[1..idx];
292 } else cmd[1..];
293
294 // TODO: StaticStringMap :)
295 if (std.mem.eql(u8, simple_cmd, "chatid")) {
296 var sb = ArrayList(u8).init(bot.allocator);
297 defer sb.deinit();
298 try sb.writer().print("<code>{}</code>", .{msg.chat.id});
299
300 try bot.sendMessage_(.{
301 .chat_id = msg.chat.id,
302 .text = sb.items,
303 .parse_mode = .html,
304 .reply_parameters = .{
305 .message_id = msg.message_id,
306 .chat_id = msg.chat.id,
307 },
308 });
309 } else if (std.mem.eql(u8, simple_cmd, "ping")) {
310 var timer = try std.time.Timer.start();
311
312 const recv = msg.date - @as(u64, @intCast(std.time.timestamp()));
313
314 var sb = ArrayList(u8).init(bot.allocator);
315 defer sb.deinit();
316 try sb.writer().print("Pong!\nReceive time: {}s\nSend time: ...", .{recv});
317
318 const reply = try bot.sendMessage(.{
319 .chat_id = msg.chat.id,
320 .text = sb.items,
321 .reply_parameters = .{
322 .message_id = msg.message_id,
323 .chat_id = msg.chat.id,
324 },
325 });
326 defer reply.deinit();
327
328 const send = @as(f64, @floatFromInt(timer.read())) / std.time.ns_per_ms;
329 sb.clearRetainingCapacity();
330 try sb.writer().print("Pong!\nReceive time: {}s\nSend time: {d}ms", .{ recv, send });
331
332 try bot.editMessageText_(.{
333 .chat_id = reply.value.chat.id,
334 .message_id = reply.value.message_id,
335 .text = sb.items,
336 });
337 } else if (std.mem.eql(u8, simple_cmd, "shutdown")) blk: {
338 if (msg.from == null or msg.from.?.id != bot.config.owner) {
339 break :blk;
340 }
341
342 bot.poweron = false;
343 try bot.sendMessage_(.{
344 .chat_id = msg.chat.id,
345 .text = "Initialising shutdown...",
346 .reply_parameters = .{
347 .allow_sending_without_reply = true,
348 .message_id = msg.message_id,
349 .chat_id = msg.chat.id,
350 },
351 });
352 }
353}
diff --git a/src/textutils.zig b/src/textutils.zig
new file mode 100644
index 0000000..41dd5f5
--- /dev/null
+++ b/src/textutils.zig
@@ -0,0 +1,16 @@
1const std = @import("std");
2
3const Allocator = std.mem.Allocator;
4const ArrayList = std.ArrayList;
5
6pub fn escapeXml(writer: anytype, text: []const u8) !void {
7 for (text) |ch| {
8 try switch (ch) {
9 '<' => writer.writeAll("&lt;"),
10 '>' => writer.writeAll("&gt;"),
11 '&' => writer.writeAll("&amp;"),
12 '"' => writer.writeAll("&quot;"),
13 else => writer.writeByte(ch),
14 };
15 }
16}
diff --git a/src/types.zig b/src/types.zig
new file mode 100644
index 0000000..ab1ab20
--- /dev/null
+++ b/src/types.zig
@@ -0,0 +1,99 @@
1pub const Animation = @import("types/Animation.zig");
2pub const Audio = @import("types/Audio.zig");
3pub const BackgroundFill = @import("types/background_fill.zig").BackgroundFill;
4pub const BackgroundType = @import("types/background_type.zig").BackgroundType;
5pub const BotName = @import("types/BotName.zig");
6pub const BusinessConnection = @import("types/BusinessConnection.zig");
7pub const BusinessMessagesDeleted = @import("types/BusinessMessagesDeleted.zig");
8pub const CallbackGame = @import("types/CallbackGame.zig");
9pub const CallbackQuery = @import("types/CallbackQuery.zig");
10pub const Chat = @import("types/Chat.zig");
11pub const ChatBackground = @import("types/ChatBackground.zig");
12pub const ChatBoost = @import("types/ChatBoost.zig");
13pub const ChatBoostAdded = @import("types/ChatBoostAdded.zig");
14pub const ChatBoostRemoved = @import("types/ChatBoostRemoved.zig");
15pub const ChatBoostSource = @import("types/chat_boost_source.zig").ChatBoostSource;
16pub const ChatBoostUpdated = @import("types/ChatBoostUpdated.zig");
17pub const ChatInviteLink = @import("types/ChatInviteLink.zig");
18pub const ChatJoinRequest = @import("types/ChatJoinRequest.zig");
19pub const ChatMember = @import("types/chat_member.zig").ChatMember;
20pub const ChatMemberUpdated = @import("types/ChatMemberUpdated.zig");
21pub const ChatShared = @import("types/ChatShared.zig");
22pub const ChosenInlineResult = @import("types/ChosenInlineResult.zig");
23pub const Contact = @import("types/Contact.zig");
24pub const Dice = @import("types/Dice.zig");
25pub const Document = @import("types/Document.zig");
26pub const EncryptedCredentials = @import("types/EncryptedCredentials.zig");
27pub const EncryptedPassportElement = @import("types/EncryptedPassportElement.zig");
28pub const ExternalReplyInfo = @import("types/ExternalReplyInfo.zig");
29pub const File = @import("types/File.zig");
30pub const ForumTopicClosed = @import("types/ForumTopicClosed.zig");
31pub const ForumTopicCreated = @import("types/ForumTopicCreated.zig");
32pub const ForumTopicEdited = @import("types/ForumTopicEdited.zig");
33pub const ForumTopicReopened = @import("types/ForumTopicReopened.zig");
34pub const Game = @import("types/Game.zig");
35pub const GeneralForumTopicHidden = @import("types/GeneralForumTopicHidden.zig");
36pub const GeneralForumTopicUnhidden = @import("types/GeneralForumTopicUnhidden.zig");
37pub const Giveaway = @import("types/Giveaway.zig");
38pub const GiveawayCompleted = @import("types/GiveawayCompleted.zig");
39pub const GiveawayCreated = @import("types/GiveawayCreated.zig");
40pub const GiveawayWinners = @import("types/GiveawayWinners.zig");
41pub const InlineKeyboardButton = @import("types/InlineKeyboardButton.zig");
42pub const InlineKeyboardMarkup = @import("types/InlineKeyboardMarkup.zig");
43pub const InlineQuery = @import("types/InlineQuery.zig");
44pub const Invoice = @import("types/Invoice.zig");
45pub const LinkPreviewOptions = @import("types/LinkPreviewOptions.zig");
46pub const Location = @import("types/Location.zig");
47pub const LoginUrl = @import("types/LoginUrl.zig");
48pub const MaskPosition = @import("types/MaskPosition.zig");
49pub const MaybeInaccessibleMessage = Message;
50pub const Message = @import("types/Message.zig");
51pub const MessageAutoDeleteTimerChanged = @import("types/MessageAutoDeleteTimerChanged.zig");
52pub const MessageEntity = @import("types/MessageEntity.zig");
53pub const MessageOrigin = @import("types/message_origin.zig").MessageOrigin;
54pub const MessageReactionCountUpdated = @import("types/MessageReactionCountUpdated.zig");
55pub const MessageReactionUpdated = @import("types/MessageReactionUpdated.zig");
56pub const OrderInfo = @import("types/OrderInfo.zig");
57pub const PaidMedia = @import("types/paid_media.zig").PaidMedia;
58pub const PaidMediaInfo = @import("types/PaidMediaInfo.zig");
59pub const PassportData = @import("types/PassportData.zig");
60pub const PassportFile = @import("types/PassportFile.zig");
61pub const PhotoSize = @import("types/PhotoSize.zig");
62pub const Poll = @import("types/Poll.zig");
63pub const PollAnswer = @import("types/PollAnswer.zig");
64pub const PollOption = @import("types/PollOption.zig");
65pub const PreCheckoutQuery = @import("types/PreCheckoutQuery.zig");
66pub const ProximityAlertTriggered = @import("types/ProximityAlertTriggered.zig");
67pub const ReactionCount = @import("types/ReactionCount.zig");
68pub const ReactionType = @import("types/reaction_type.zig").ReactionType;
69pub const RefundedPayment = @import("types/RefundedPayment.zig");
70pub const ReplyParameters = @import("types/ReplyParameters.zig");
71pub const ResponseParameters = @import("types/ResponseParameters.zig");
72pub const SharedUser = @import("types/SharedUser.zig");
73pub const ShippingAddress = @import("types/ShippingAddress.zig");
74pub const ShippingQuery = @import("types/ShippingQuery.zig");
75pub const Sticker = @import("types/Sticker.zig");
76pub const Story = @import("types/Story.zig");
77pub const SuccessfulPayment = @import("types/SuccessfulPayment.zig");
78pub const SwitchInlineQueryChosenChat = @import("types/SwitchInlineQueryChosenChat.zig");
79pub const TextQuote = @import("types/TextQuote.zig");
80pub const Update = @import("types/Update.zig");
81pub const User = @import("types/User.zig");
82pub const UsersShared = @import("types/UsersShared.zig");
83pub const Venue = @import("types/Venue.zig");
84pub const Video = @import("types/Video.zig");
85pub const VideoChatEnded = @import("types/VideoChatEnded.zig");
86pub const VideoChatParticipantsInvited = @import("types/VideoChatParticipantsInvited.zig");
87pub const VideoChatScheduled = @import("types/VideoChatScheduled.zig");
88pub const VideoChatStarted = @import("types/VideoChatStarted.zig");
89pub const VideoNote = @import("types/VideoNote.zig");
90pub const Voice = @import("types/Voice.zig");
91pub const WebAppData = @import("types/WebAppData.zig");
92pub const WebAppInfo = @import("types/WebAppInfo.zig");
93pub const WriteAccessAllowed = @import("types/WriteAccessAllowed.zig");
94
95pub const EditMessageTextParams = @import("types/EditMessageTextParams.zig");
96pub const GetMyNameParams = @import("types/GetMyNameParams.zig");
97pub const GetUpdatesParams = @import("types/GetUpdatesParams.zig");
98pub const SendMessageParams = @import("types/SendMessageParams.zig");
99pub const SetMyNameParams = @import("types/SetMyNameParams.zig");
diff --git a/src/types/Animation.zig b/src/types/Animation.zig
new file mode 100644
index 0000000..f5d6efc
--- /dev/null
+++ b/src/types/Animation.zig
@@ -0,0 +1,11 @@
1const PhotoSize = @import("PhotoSize.zig");
2
3file_id: []const u8,
4file_unique_id: []const u8,
5width: u64,
6height: u64,
7duration: u64,
8thumbnail: ?[]PhotoSize = null,
9file_name: ?[]const u8 = null,
10mime_type: ?[]const u8 = null,
11file_size: ?u64 = null,
diff --git a/src/types/Audio.zig b/src/types/Audio.zig
new file mode 100644
index 0000000..1914088
--- /dev/null
+++ b/src/types/Audio.zig
@@ -0,0 +1,11 @@
1const PhotoSize = @import("PhotoSize.zig");
2
3file_id: []const u8,
4file_unique_id: []const u8,
5duration: u64,
6performer: ?[]const u8 = null,
7title: ?[]const u8 = null,
8file_name: ?[]const u8 = null,
9mime_type: ?[]const u8 = null,
10file_size: ?u64 = null,
11thumbnail: ?PhotoSize = null,
diff --git a/src/types/BotName.zig b/src/types/BotName.zig
new file mode 100644
index 0000000..61a2cb6
--- /dev/null
+++ b/src/types/BotName.zig
@@ -0,0 +1 @@
name: []const u8,
diff --git a/src/types/BusinessConnection.zig b/src/types/BusinessConnection.zig
new file mode 100644
index 0000000..0801235
--- /dev/null
+++ b/src/types/BusinessConnection.zig
@@ -0,0 +1,8 @@
1const User = @import("User.zig");
2
3id: []const u8,
4user: User,
5user_chat_id: i64,
6date: u64,
7can_reply: bool,
8is_enabled: bool,
diff --git a/src/types/BusinessMessagesDeleted.zig b/src/types/BusinessMessagesDeleted.zig
new file mode 100644
index 0000000..ef55793
--- /dev/null
+++ b/src/types/BusinessMessagesDeleted.zig
@@ -0,0 +1,5 @@
1const Chat = @import("Chat.zig");
2
3business_connection_id: []const u8,
4chat: Chat,
5message_ids: []u64,
diff --git a/src/types/CallbackGame.zig b/src/types/CallbackGame.zig
new file mode 100644
index 0000000..2f72c10
--- /dev/null
+++ b/src/types/CallbackGame.zig
@@ -0,0 +1 @@
// meant to be empty
diff --git a/src/types/CallbackQuery.zig b/src/types/CallbackQuery.zig
new file mode 100644
index 0000000..8fb96c3
--- /dev/null
+++ b/src/types/CallbackQuery.zig
@@ -0,0 +1,10 @@
1const MaybeInaccessibleMessage = @import("Message.zig");
2const User = @import("User.zig");
3
4id: []const u8,
5from: User,
6message: ?MaybeInaccessibleMessage = null,
7inline_message_id: ?[]const u8 = null,
8chat_instance: []const u8,
9data: ?[]const u8 = null,
10game_short_name: ?[]const u8 = null,
diff --git a/src/types/Chat.zig b/src/types/Chat.zig
new file mode 100644
index 0000000..f44e480
--- /dev/null
+++ b/src/types/Chat.zig
@@ -0,0 +1,14 @@
1pub const Type = enum {
2 private,
3 group,
4 supergroup,
5 channel,
6};
7
8id: i64,
9type: Type,
10title: ?[]const u8 = null,
11username: ?[]const u8 = null,
12first_name: ?[]const u8 = null,
13last_name: ?[]const u8 = null,
14is_forum: bool = false,
diff --git a/src/types/ChatBackground.zig b/src/types/ChatBackground.zig
new file mode 100644
index 0000000..34d2e15
--- /dev/null
+++ b/src/types/ChatBackground.zig
@@ -0,0 +1,3 @@
1const BackgroundType = @import("background_type.zig").BackgroundType;
2
3type: BackgroundType,
diff --git a/src/types/ChatBoost.zig b/src/types/ChatBoost.zig
new file mode 100644
index 0000000..353805b
--- /dev/null
+++ b/src/types/ChatBoost.zig
@@ -0,0 +1,6 @@
1const ChatBoostSource = @import("chat_boost_source.zig").ChatBoostSource;
2
3boost_id: []const u8,
4add_date: u64,
5expiration_date: u64,
6source: ChatBoostSource,
diff --git a/src/types/ChatBoostAdded.zig b/src/types/ChatBoostAdded.zig
new file mode 100644
index 0000000..487ae70
--- /dev/null
+++ b/src/types/ChatBoostAdded.zig
@@ -0,0 +1 @@
boost_count: u64,
diff --git a/src/types/ChatBoostRemoved.zig b/src/types/ChatBoostRemoved.zig
new file mode 100644
index 0000000..0d61bc5
--- /dev/null
+++ b/src/types/ChatBoostRemoved.zig
@@ -0,0 +1,7 @@
1const Chat = @import("Chat.zig");
2const ChatBoostSource = @import("chat_boost_source.zig").ChatBoostSource;
3
4chat: Chat,
5boost_id: []const u8,
6remove_date: u64,
7source: ChatBoostSource,
diff --git a/src/types/ChatBoostUpdated.zig b/src/types/ChatBoostUpdated.zig
new file mode 100644
index 0000000..e7ad33c
--- /dev/null
+++ b/src/types/ChatBoostUpdated.zig
@@ -0,0 +1,5 @@
1const Chat = @import("Chat.zig");
2const ChatBoost = @import("ChatBoost.zig");
3
4chat: Chat,
5boost: ChatBoost,
diff --git a/src/types/ChatInviteLink.zig b/src/types/ChatInviteLink.zig
new file mode 100644
index 0000000..41a1f44
--- /dev/null
+++ b/src/types/ChatInviteLink.zig
@@ -0,0 +1,11 @@
1const User = @import("User.zig");
2
3invite_link: []const u8,
4creator: User,
5creates_join_request: bool,
6is_primary: bool,
7is_revoked: bool,
8name: ?[]const u8,
9expire_date: ?u64,
10member_limit: ?u64,
11pending_join_request_count: ?u64,
diff --git a/src/types/ChatJoinRequest.zig b/src/types/ChatJoinRequest.zig
new file mode 100644
index 0000000..f08e070
--- /dev/null
+++ b/src/types/ChatJoinRequest.zig
@@ -0,0 +1,10 @@
1const Chat = @import("Chat.zig");
2const ChatInviteLink = @import("ChatInviteLink.zig");
3const User = @import("User.zig");
4
5chat: Chat,
6from: User,
7user_chat_id: i64,
8date: u64,
9bio: ?[]const u8,
10invite_link: ?ChatInviteLink,
diff --git a/src/types/ChatMemberUpdated.zig b/src/types/ChatMemberUpdated.zig
new file mode 100644
index 0000000..8df56ec
--- /dev/null
+++ b/src/types/ChatMemberUpdated.zig
@@ -0,0 +1,13 @@
1const Chat = @import("Chat.zig");
2const ChatInviteLink = @import("ChatInviteLink.zig");
3const ChatMember = @import("chat_member.zig").ChatMember;
4const User = @import("User.zig");
5
6chat: Chat,
7from: User,
8date: u64,
9old_chat_member: ChatMember,
10new_chat_member: ChatMember,
11invite_link: ?ChatInviteLink = null,
12via_join_request: bool = false,
13via_chat_folder_invite_link: bool = false,
diff --git a/src/types/ChatShared.zig b/src/types/ChatShared.zig
new file mode 100644
index 0000000..7b49f02
--- /dev/null
+++ b/src/types/ChatShared.zig
@@ -0,0 +1,7 @@
1const PhotoSize = @import("PhotoSize.zig");
2
3request_id: u64,
4chat_id: i64,
5title: ?[]const u8 = null,
6username: ?[]const u8 = null,
7photo: ?[]PhotoSize = null,
diff --git a/src/types/ChosenInlineResult.zig b/src/types/ChosenInlineResult.zig
new file mode 100644
index 0000000..e0528cd
--- /dev/null
+++ b/src/types/ChosenInlineResult.zig
@@ -0,0 +1,8 @@
1const Location = @import("Location.zig");
2const User = @import("User.zig");
3
4result_id: []const u8,
5from: User,
6location: ?Location = null,
7inline_message_id: ?[]const u8,
8query: []const u8,
diff --git a/src/types/Contact.zig b/src/types/Contact.zig
new file mode 100644
index 0000000..e3000fa
--- /dev/null
+++ b/src/types/Contact.zig
@@ -0,0 +1,5 @@
1phone_number: []const u8,
2first_name: []const u8,
3last_name: ?[]const u8 = null,
4user_id: ?[]u64 = null,
5vcard: ?[]const u8 = null,
diff --git a/src/types/Dice.zig b/src/types/Dice.zig
new file mode 100644
index 0000000..9aa6b06
--- /dev/null
+++ b/src/types/Dice.zig
@@ -0,0 +1,2 @@
1emoji: []const u8,
2value: u8,
diff --git a/src/types/Document.zig b/src/types/Document.zig
new file mode 100644
index 0000000..811273d
--- /dev/null
+++ b/src/types/Document.zig
@@ -0,0 +1,8 @@
1const PhotoSize = @import("PhotoSize.zig");
2
3file_id: []const u8,
4file_unique_id: []const u8,
5thumbnail: ?PhotoSize = null,
6file_name: ?[]const u8,
7mime_type: ?[]const u8,
8file_size: ?u64 = null,
diff --git a/src/types/EditMessageTextParams.zig b/src/types/EditMessageTextParams.zig
new file mode 100644
index 0000000..3e7701f
--- /dev/null
+++ b/src/types/EditMessageTextParams.zig
@@ -0,0 +1,16 @@
1const InlineKeyboardMarkup = @import("InlineKeyboardMarkup.zig");
2const LinkPreviewOptions = @import("LinkPreviewOptions.zig");
3const MessageEntity = @import("MessageEntity.zig");
4const ParseMode = @import("parse_mode.zig").ParseMode;
5
6business_connection_id: ?[]const u8 = null,
7// TODO: Integer or String
8chat_id: i64,
9message_id: u64,
10// TODO: Make a different call for this bcs it returns just true :D
11// inline_message_id: ?u64 = null,
12text: []const u8,
13parse_mode: ?ParseMode = null,
14entities: ?[]MessageEntity = null,
15link_preview_options: ?LinkPreviewOptions = null,
16reply_markup: ?InlineKeyboardMarkup = null,
diff --git a/src/types/EncryptedCredentials.zig b/src/types/EncryptedCredentials.zig
new file mode 100644
index 0000000..6c87689
--- /dev/null
+++ b/src/types/EncryptedCredentials.zig
@@ -0,0 +1,3 @@
1data: []const u8,
2hash: []const u8,
3secret: []const u8,
diff --git a/src/types/EncryptedPassportElement.zig b/src/types/EncryptedPassportElement.zig
new file mode 100644
index 0000000..ca0e2ed
--- /dev/null
+++ b/src/types/EncryptedPassportElement.zig
@@ -0,0 +1,30 @@
1// TODO: Turn this into a tagged union
2
3const PassportFile = @import("PassportFile.zig");
4
5pub const Type = enum {
6 personal_details,
7 passport,
8 driver_license,
9 identity_card,
10 internal_passport,
11 address,
12 utility_bill,
13 bank_statement,
14 rental_agreement,
15 passport_registration,
16 temporary_registration,
17 phone_number,
18 email,
19};
20
21type: Type,
22data: ?[]const u8,
23phone_number: ?[]const u8,
24email: ?[]const u8,
25files: ?[]PassportFile,
26front_side: ?PassportFile,
27reverse_side: ?PassportFile,
28selfie: ?PassportFile,
29translation: ?[]PassportFile,
30hash: []const u8,
diff --git a/src/types/ExternalReplyInfo.zig b/src/types/ExternalReplyInfo.zig
new file mode 100644
index 0000000..89aa004
--- /dev/null
+++ b/src/types/ExternalReplyInfo.zig
@@ -0,0 +1,47 @@
1const Animation = @import("Animation.zig");
2const Audio = @import("Audio.zig");
3const Chat = @import("Chat.zig");
4const Contact = @import("Contact.zig");
5const Dice = @import("Dice.zig");
6const Document = @import("Document.zig");
7const Game = @import("Game.zig");
8const Giveaway = @import("Giveaway.zig");
9const GiveawayWinners = @import("GiveawayWinners.zig");
10const Invoice = @import("Invoice.zig");
11const LinkPreviewOptions = @import("LinkPreviewOptions.zig");
12const Location = @import("Location.zig");
13const MessageOrigin = @import("message_origin.zig").MessageOrigin;
14const PaidMediaInfo = @import("PaidMediaInfo.zig");
15const Poll = @import("Poll.zig");
16const PhotoSize = @import("PhotoSize.zig");
17const Sticker = @import("Sticker.zig");
18const Story = @import("Story.zig");
19const Venue = @import("Venue.zig");
20const Video = @import("Video.zig");
21const VideoNote = @import("VideoNote.zig");
22const Voice = @import("Voice.zig");
23
24origin: MessageOrigin,
25chat: ?Chat = null,
26message_id: ?u64 = null,
27link_preview_options: ?LinkPreviewOptions = null,
28animation: ?Animation = null,
29audio: ?Audio = null,
30document: ?Document = null,
31paid_media: ?PaidMediaInfo = null,
32photo: ?[]PhotoSize = null,
33sticker: ?Sticker = null,
34story: ?Story = null,
35video: ?Video = null,
36video_note: ?VideoNote = null,
37voice: ?Voice = null,
38has_media_spoiler: bool = false,
39contact: ?Contact = null,
40dice: ?Dice = null,
41game: ?Game = null,
42giveaway: ?Giveaway = null,
43giveaway_winners: ?GiveawayWinners = null,
44invoice: ?Invoice = null,
45location: ?Location = null,
46poll: ?Poll = null,
47venue: ?Venue = null,
diff --git a/src/types/File.zig b/src/types/File.zig
new file mode 100644
index 0000000..3555bc9
--- /dev/null
+++ b/src/types/File.zig
@@ -0,0 +1,4 @@
1file_id: []const u8,
2file_unique_id: []const u8,
3file_size: ?u64 = null,
4file_path: ?[]const u8 = null,
diff --git a/src/types/ForumTopicClosed.zig b/src/types/ForumTopicClosed.zig
new file mode 100644
index 0000000..2f72c10
--- /dev/null
+++ b/src/types/ForumTopicClosed.zig
@@ -0,0 +1 @@
// meant to be empty
diff --git a/src/types/ForumTopicCreated.zig b/src/types/ForumTopicCreated.zig
new file mode 100644
index 0000000..2429de7
--- /dev/null
+++ b/src/types/ForumTopicCreated.zig
@@ -0,0 +1,3 @@
1name: []const u8,
2icon_color: u24,
3icon_custom_emoji_id: ?[]const u8 = null,
diff --git a/src/types/ForumTopicEdited.zig b/src/types/ForumTopicEdited.zig
new file mode 100644
index 0000000..097acb3
--- /dev/null
+++ b/src/types/ForumTopicEdited.zig
@@ -0,0 +1,2 @@
1name: ?[]const u8 = null,
2icon_custom_emoji_id: ?[]const u8 = null,
diff --git a/src/types/ForumTopicReopened.zig b/src/types/ForumTopicReopened.zig
new file mode 100644
index 0000000..2f72c10
--- /dev/null
+++ b/src/types/ForumTopicReopened.zig
@@ -0,0 +1 @@
// meant to be empty
diff --git a/src/types/Game.zig b/src/types/Game.zig
new file mode 100644
index 0000000..3738284
--- /dev/null
+++ b/src/types/Game.zig
@@ -0,0 +1,10 @@
1const Animation = @import("Animation.zig");
2const MessageEntity = @import("MessageEntity.zig");
3const PhotoSize = @import("PhotoSize.zig");
4
5title: []const u8,
6description: []const u8,
7photo: []PhotoSize,
8text: ?[]const u8 = null,
9text_entities: ?[]MessageEntity = null,
10animation: ?Animation = null,
diff --git a/src/types/GeneralForumTopicHidden.zig b/src/types/GeneralForumTopicHidden.zig
new file mode 100644
index 0000000..2f72c10
--- /dev/null
+++ b/src/types/GeneralForumTopicHidden.zig
@@ -0,0 +1 @@
// meant to be empty
diff --git a/src/types/GeneralForumTopicUnhidden.zig b/src/types/GeneralForumTopicUnhidden.zig
new file mode 100644
index 0000000..2f72c10
--- /dev/null
+++ b/src/types/GeneralForumTopicUnhidden.zig
@@ -0,0 +1 @@
// meant to be empty
diff --git a/src/types/GetMyNameParams.zig b/src/types/GetMyNameParams.zig
new file mode 100644
index 0000000..2cb38fa
--- /dev/null
+++ b/src/types/GetMyNameParams.zig
@@ -0,0 +1 @@
language_code: ?[]const u8 = null
diff --git a/src/types/GetUpdatesParams.zig b/src/types/GetUpdatesParams.zig
new file mode 100644
index 0000000..0a4e333
--- /dev/null
+++ b/src/types/GetUpdatesParams.zig
@@ -0,0 +1,4 @@
1offset: ?u64 = null,
2limit: ?u64 = null,
3timeout: ?u64 = null,
4allowed_updates: ?[]const u8 = null,
diff --git a/src/types/Giveaway.zig b/src/types/Giveaway.zig
new file mode 100644
index 0000000..d1cfa7e
--- /dev/null
+++ b/src/types/Giveaway.zig
@@ -0,0 +1,10 @@
1const Chat = @import("Chat.zig");
2
3chats: []Chat,
4winners_selection_date: u64,
5winner_count: u64,
6only_new_members: bool = false,
7has_public_winners: bool = false,
8prize_description: ?[]const u8 = null,
9country_codes: ?[]const u8 = null,
10premium_subscription_month_count: ?u64 = null,
diff --git a/src/types/GiveawayCompleted.zig b/src/types/GiveawayCompleted.zig
new file mode 100644
index 0000000..9e07ad9
--- /dev/null
+++ b/src/types/GiveawayCompleted.zig
@@ -0,0 +1,5 @@
1const Message = @import("Message.zig");
2
3winner_count: u64,
4unclaimed_prize_count: ?u64 = null,
5giveaway_message: ?Message = null,
diff --git a/src/types/GiveawayCreated.zig b/src/types/GiveawayCreated.zig
new file mode 100644
index 0000000..2f72c10
--- /dev/null
+++ b/src/types/GiveawayCreated.zig
@@ -0,0 +1 @@
// meant to be empty
diff --git a/src/types/GiveawayWinners.zig b/src/types/GiveawayWinners.zig
new file mode 100644
index 0000000..b99a7d2
--- /dev/null
+++ b/src/types/GiveawayWinners.zig
@@ -0,0 +1,14 @@
1const Chat = @import("Chat.zig");
2const User = @import("User.zig");
3
4chat: Chat,
5giveaway_message_id: u64,
6winners_selection_date: u64,
7winner_count: u64,
8winners: []User,
9additional_chat_count: ?u64 = null,
10premium_subscription_month_count: ?u64 = null,
11unclaimed_prize_count: ?u64 = null,
12only_new_members: bool = false,
13was_refunded: bool = false,
14prize_description: ?[]const u8 = null,
diff --git a/src/types/InlineKeyboardButton.zig b/src/types/InlineKeyboardButton.zig
new file mode 100644
index 0000000..be156b4
--- /dev/null
+++ b/src/types/InlineKeyboardButton.zig
@@ -0,0 +1,15 @@
1const CallbackGame = @import("CallbackGame.zig");
2const LoginUrl = @import("LoginUrl.zig");
3const SwitchInlineQueryChosenChat = @import("SwitchInlineQueryChosenChat.zig");
4const WebAppInfo = @import("WebAppInfo.zig");
5
6text: []const u8,
7url: ?[]const u8 = null,
8callback_data: ?[]const u8 = null,
9web_app: ?WebAppInfo = null,
10login_url: ?LoginUrl = null,
11switch_inline_query: ?[]const u8 = null,
12switch_inline_query_current_chat: ?[]const u8 = null,
13switch_inline_query_chosen_chat: ?SwitchInlineQueryChosenChat = null,
14callback_game: ?CallbackGame = null,
15pay: bool = false,
diff --git a/src/types/InlineKeyboardMarkup.zig b/src/types/InlineKeyboardMarkup.zig
new file mode 100644
index 0000000..388d4fc
--- /dev/null
+++ b/src/types/InlineKeyboardMarkup.zig
@@ -0,0 +1,3 @@
1const InlineKeyboardButton = @import("InlineKeyboardButton.zig");
2
3inline_keyboard: [][]InlineKeyboardButton,
diff --git a/src/types/InlineQuery.zig b/src/types/InlineQuery.zig
new file mode 100644
index 0000000..7cbbe20
--- /dev/null
+++ b/src/types/InlineQuery.zig
@@ -0,0 +1,17 @@
1const Location = @import("Location.zig");
2const User = @import("User.zig");
3
4pub const ChatType = enum {
5 sender,
6 private,
7 group,
8 supergroup,
9 channel,
10};
11
12id: []const u8,
13from: User,
14query: []const u8,
15offset: []const u8,
16chat_type: ?ChatType,
17location: ?Location,
diff --git a/src/types/Invoice.zig b/src/types/Invoice.zig
new file mode 100644
index 0000000..5ff6bfd
--- /dev/null
+++ b/src/types/Invoice.zig
@@ -0,0 +1,5 @@
1title: []const u8,
2description: []const u8,
3start_parameter: []const u8,
4currency: []const u8,
5total_amount: u64,
diff --git a/src/types/LinkPreviewOptions.zig b/src/types/LinkPreviewOptions.zig
new file mode 100644
index 0000000..c8258e7
--- /dev/null
+++ b/src/types/LinkPreviewOptions.zig
@@ -0,0 +1,5 @@
1is_disabled: ?bool = null,
2url: ?[]const u8 = null,
3prefer_small_media: ?bool = null,
4prefer_large_media: ?bool = null,
5show_above_text: ?bool = null,
diff --git a/src/types/Location.zig b/src/types/Location.zig
new file mode 100644
index 0000000..1fef4e3
--- /dev/null
+++ b/src/types/Location.zig
@@ -0,0 +1,6 @@
1latitude: f32,
2longitude: f32,
3horizontal_accuracy: ?f32 = null,
4live_period: ?u64 = null,
5heading: ?u64 = null,
6proximity_alert_radius: ?u64 = null,
diff --git a/src/types/LoginUrl.zig b/src/types/LoginUrl.zig
new file mode 100644
index 0000000..37b89f6
--- /dev/null
+++ b/src/types/LoginUrl.zig
@@ -0,0 +1,4 @@
1url: []const u8,
2forward_text: ?[]const u8 = null,
3bot_username: ?[]const u8 = null,
4request_write_access: ?[]const u8 = null,
diff --git a/src/types/MaskPosition.zig b/src/types/MaskPosition.zig
new file mode 100644
index 0000000..ba668c4
--- /dev/null
+++ b/src/types/MaskPosition.zig
@@ -0,0 +1,11 @@
1pub const Point = enum {
2 forehead,
3 eyes,
4 mouth,
5 chin,
6};
7
8point: Point,
9x_shift: f32,
10y_shift: f32,
11scale: f32,
diff --git a/src/types/Message.zig b/src/types/Message.zig
new file mode 100644
index 0000000..448ca86
--- /dev/null
+++ b/src/types/Message.zig
@@ -0,0 +1,139 @@
1const Animation = @import("Animation.zig");
2const Audio = @import("Audio.zig");
3const Chat = @import("Chat.zig");
4const ChatBackground = @import("ChatBackground.zig");
5const ChatBoostAdded = @import("ChatBoostAdded.zig");
6const ChatShared = @import("ChatShared.zig");
7const Contact = @import("Contact.zig");
8const Dice = @import("Dice.zig");
9const Document = @import("Document.zig");
10const ExternalReplyInfo = @import("ExternalReplyInfo.zig");
11const ForumTopicClosed = @import("ForumTopicClosed.zig");
12const ForumTopicCreated = @import("ForumTopicCreated.zig");
13const ForumTopicEdited = @import("ForumTopicEdited.zig");
14const ForumTopicReopened = @import("ForumTopicReopened.zig");
15const Game = @import("Game.zig");
16const GeneralForumTopicHidden = @import("GeneralForumTopicHidden.zig");
17const GeneralForumTopicUnhidden = @import("GeneralForumTopicUnhidden.zig");
18const Giveaway = @import("Giveaway.zig");
19const GiveawayCompleted = @import("GiveawayCompleted.zig");
20const GiveawayCreated = @import("GiveawayCreated.zig");
21const GiveawayWinners = @import("GiveawayWinners.zig");
22const InlineKeyboardMarkup = @import("InlineKeyboardMarkup.zig");
23const Invoice = @import("Invoice.zig");
24const LinkPreviewOptions = @import("LinkPreviewOptions.zig");
25const Location = @import("Location.zig");
26const MaybeInaccessibleMessage = @This();
27const Message = @This();
28const MessageAutoDeleteTimerChanged = @import("MessageAutoDeleteTimerChanged.zig");
29const MessageEntity = @import("MessageEntity.zig");
30const MessageOrigin = @import("message_origin.zig").MessageOrigin;
31const PaidMediaInfo = @import("PaidMediaInfo.zig");
32const PassportData = @import("PassportData.zig");
33const PhotoSize = @import("PhotoSize.zig");
34const Poll = @import("Poll.zig");
35const ProximityAlertTriggered = @import("ProximityAlertTriggered.zig");
36const RefundedPayment = @import("RefundedPayment.zig");
37const Sticker = @import("Sticker.zig");
38const Story = @import("Story.zig");
39const SuccessfulPayment = @import("SuccessfulPayment.zig");
40const TextQuote = @import("TextQuote.zig");
41const User = @import("User.zig");
42const UsersShared = @import("UsersShared.zig");
43const Venue = @import("Venue.zig");
44const Video = @import("Video.zig");
45const VideoChatEnded = @import("VideoChatEnded.zig");
46const VideoChatParticipantsInvited = @import("VideoChatParticipantsInvited.zig");
47const VideoChatScheduled = @import("VideoChatScheduled.zig");
48const VideoChatStarted = @import("VideoChatStarted.zig");
49const VideoNote = @import("VideoNote.zig");
50const Voice = @import("Voice.zig");
51const WebAppData = @import("WebAppData.zig");
52const WriteAccessAllowed = @import("WriteAccessAllowed.zig");
53
54message_id: u64,
55message_thread_id: ?u64 = null,
56from: ?User = null,
57sender_chat: ?Chat = null,
58sender_boost_count: ?u64 = null,
59sender_business_bot: ?User = null,
60// If this is a MaybeInaccessibleMessage this will be 0 if this is inaccessible
61date: u64,
62business_connection_id: ?[]const u8 = null,
63chat: Chat,
64forward_origin: ?MessageOrigin = null,
65is_topic_message: bool = false,
66is_automatic_forward: bool = false,
67reply_to_message: ?*Message = null,
68external_reply: ?ExternalReplyInfo = null,
69quote: ?TextQuote = null,
70reply_to_story: ?Story = null,
71via_bot: ?User = null,
72edit_date: ?u64 = null,
73has_protected_content: bool = false,
74is_from_offline: bool = false,
75media_group_id: ?[]const u8 = null,
76author_signature: ?[]const u8 = null,
77text: ?[]const u8 = null,
78entities: ?[]MessageEntity = null,
79link_preview_options: ?LinkPreviewOptions = null,
80effect_id: ?[]const u8 = null,
81animation: ?Animation = null,
82audio: ?Audio = null,
83document: ?Document = null,
84paid_media: ?PaidMediaInfo = null,
85photo: ?[]PhotoSize = null,
86sticker: ?Sticker = null,
87story: ?Story = null,
88video: ?Video = null,
89video_note: ?VideoNote = null,
90voice: ?Voice = null,
91caption: ?[]const u8 = null,
92caption_entities: ?[]MessageEntity = null,
93show_caption_above_media: bool = false,
94has_media_spoiler: bool = false,
95contact: ?Contact = null,
96dice: ?Dice = null,
97game: ?Game = null,
98poll: ?Poll = null,
99venue: ?Venue = null,
100location: ?Location = null,
101new_chat_members: ?[]User = null,
102left_chat_member: ?User = null,
103new_chat_title: ?[]const u8 = null,
104new_chat_photo: ?[]PhotoSize = null,
105delete_chat_photo: bool = false,
106group_chat_created: bool = false,
107supergroup_chat_created: bool = false,
108channel_chat_created: bool = false,
109message_auto_delete_timer_changed: ?MessageAutoDeleteTimerChanged = null,
110migrate_to_chat_id: ?i64 = null,
111migrate_from_chat_id: ?i64 = null,
112pinned_message: ?*MaybeInaccessibleMessage = null,
113invoice: ?Invoice = null,
114successful_payment: ?SuccessfulPayment = null,
115refunded_payment: ?RefundedPayment = null,
116users_shared: ?UsersShared = null,
117chat_shared: ?ChatShared = null,
118connected_website: ?[]const u8 = null,
119write_access_allowed: ?WriteAccessAllowed = null,
120passport_data: ?PassportData = null,
121proximity_alert_triggered: ?ProximityAlertTriggered = null,
122boost_added: ?ChatBoostAdded = null,
123chat_background_set: ?ChatBackground = null,
124forum_topic_created: ?ForumTopicCreated = null,
125forum_topic_edited: ?ForumTopicEdited = null,
126forum_topic_closed: ?ForumTopicClosed = null,
127forum_topic_reopened: ?ForumTopicReopened = null,
128general_forum_topic_hidden: ?GeneralForumTopicHidden = null,
129general_forum_topic_unhidden: ?GeneralForumTopicUnhidden = null,
130giveaway_created: ?GiveawayCreated = null,
131giveaway: ?Giveaway = null,
132giveaway_winners: ?GiveawayWinners = null,
133giveaway_completed: ?*GiveawayCompleted = null,
134video_chat_scheduled: ?VideoChatScheduled = null,
135video_chat_started: ?VideoChatStarted = null,
136video_chat_ended: ?VideoChatEnded = null,
137video_chat_participants_invited: ?VideoChatParticipantsInvited = null,
138web_app_data: ?WebAppData = null,
139reply_markup: ?InlineKeyboardMarkup = null,
diff --git a/src/types/MessageAutoDeleteTimerChanged.zig b/src/types/MessageAutoDeleteTimerChanged.zig
new file mode 100644
index 0000000..c73e682
--- /dev/null
+++ b/src/types/MessageAutoDeleteTimerChanged.zig
@@ -0,0 +1 @@
message_auto_delete_time: u64,
diff --git a/src/types/MessageEntity.zig b/src/types/MessageEntity.zig
new file mode 100644
index 0000000..1c44d20
--- /dev/null
+++ b/src/types/MessageEntity.zig
@@ -0,0 +1,66 @@
1const std = @import("std");
2
3const MessageEntity = @This();
4const User = @import("User.zig");
5const Utf8View = std.unicode.Utf8View;
6
7pub const Type = enum {
8 mention,
9 hashtag,
10 cashtag,
11 bot_command,
12 url,
13 email,
14 phone_number,
15 bold,
16 italic,
17 underline,
18 strikethrough,
19 spoiler,
20 blockquote,
21 expandable_blockquote,
22 code,
23 pre,
24 text_link,
25 text_mention,
26 custom_emoji,
27};
28
29type: Type,
30offset: u64,
31length: u64,
32url: ?[]const u8 = null,
33user: ?User = null,
34language: ?[]const u8 = null,
35custom_emoji_id: ?[]const u8 = null,
36
37pub fn extract(self: MessageEntity, src: []const u8) ![]const u8 {
38 if (self.length == 0) {
39 return "";
40 }
41
42 var utf8 = (try Utf8View.init(src)).iterator();
43 var i: usize = 0;
44
45 const start = if (i >= self.offset)
46 utf8.i
47 else blk: {
48 while (utf8.nextCodepoint()) |cp| {
49 i += std.unicode.utf16CodepointSequenceLength(cp) catch unreachable;
50 if (i >= self.offset) {
51 break :blk utf8.i;
52 }
53 }
54 return "";
55 };
56
57 i = 0;
58 while (utf8.nextCodepoint()) |cp| {
59 i += std.unicode.utf16CodepointSequenceLength(cp) catch unreachable;
60 if (i >= self.length) {
61 return src[start..utf8.i];
62 }
63 }
64
65 return src[start..];
66}
diff --git a/src/types/MessageReactionCountUpdated.zig b/src/types/MessageReactionCountUpdated.zig
new file mode 100644
index 0000000..48a9f24
--- /dev/null
+++ b/src/types/MessageReactionCountUpdated.zig
@@ -0,0 +1,7 @@
1const Chat = @import("Chat.zig");
2const ReactionCount = @import("ReactionCount.zig");
3
4chat: Chat,
5message_id: u64,
6date: u64,
7reactions: []ReactionCount,
diff --git a/src/types/MessageReactionUpdated.zig b/src/types/MessageReactionUpdated.zig
new file mode 100644
index 0000000..7da679b
--- /dev/null
+++ b/src/types/MessageReactionUpdated.zig
@@ -0,0 +1,11 @@
1const Chat = @import("Chat.zig");
2const ReactionType = @import("reaction_type.zig").ReactionType;
3const User = @import("User.zig");
4
5chat: Chat,
6message_id: u64,
7user: ?User = null,
8actor_chat: ?Chat = null,
9date: u64,
10old_reaction: []ReactionType,
11new_reaction: []ReactionType,
diff --git a/src/types/OrderInfo.zig b/src/types/OrderInfo.zig
new file mode 100644
index 0000000..3f94376
--- /dev/null
+++ b/src/types/OrderInfo.zig
@@ -0,0 +1,6 @@
1const ShippingAddress = @import("ShippingAddress.zig");
2
3name: ?[]const u8 = null,
4phone_number: ?[]const u8 = null,
5email: ?[]const u8 = null,
6shipping_address: ?ShippingAddress = null,
diff --git a/src/types/PaidMediaInfo.zig b/src/types/PaidMediaInfo.zig
new file mode 100644
index 0000000..6de7447
--- /dev/null
+++ b/src/types/PaidMediaInfo.zig
@@ -0,0 +1,4 @@
1const PaidMedia = @import("paid_media.zig").PaidMedia;
2
3star_count: u64,
4paid_media: []PaidMedia,
diff --git a/src/types/PassportData.zig b/src/types/PassportData.zig
new file mode 100644
index 0000000..679534c
--- /dev/null
+++ b/src/types/PassportData.zig
@@ -0,0 +1,5 @@
1const EncryptedCredentials = @import("EncryptedCredentials.zig");
2const EncryptedPassportElement = @import("EncryptedPassportElement.zig");
3
4data: []EncryptedPassportElement,
5credentials: EncryptedCredentials,
diff --git a/src/types/PassportFile.zig b/src/types/PassportFile.zig
new file mode 100644
index 0000000..2b208fa
--- /dev/null
+++ b/src/types/PassportFile.zig
@@ -0,0 +1,4 @@
1file_id: []const u8,
2file_unique_id: []const u8,
3file_size: u64,
4file_date: u64,
diff --git a/src/types/PhotoSize.zig b/src/types/PhotoSize.zig
new file mode 100644
index 0000000..887b530
--- /dev/null
+++ b/src/types/PhotoSize.zig
@@ -0,0 +1,5 @@
1file_id: []const u8,
2file_unique_id: []const u8,
3width: u64,
4height: u64,
5file_size: ?u64 = null,
diff --git a/src/types/Poll.zig b/src/types/Poll.zig
new file mode 100644
index 0000000..ccc3b68
--- /dev/null
+++ b/src/types/Poll.zig
@@ -0,0 +1,22 @@
1const MessageEntity = @import("MessageEntity.zig");
2const PollOption = @import("PollOption.zig");
3
4pub const Type = enum {
5 regular,
6 quiz,
7};
8
9id: []const u8,
10question: []const u8,
11question_entities: ?[]MessageEntity = null,
12options: []PollOption,
13total_voter_count: u64,
14is_closed: bool,
15is_anonymous: bool,
16type: Type,
17allows_multiple_answers: bool,
18correct_option_id: ?u64 = null,
19explanation: ?[]const u8 = null,
20explanation_entities: ?[]MessageEntity = null,
21open_period: ?u64 = null,
22close_date: ?u64 = null,
diff --git a/src/types/PollAnswer.zig b/src/types/PollAnswer.zig
new file mode 100644
index 0000000..7d4985c
--- /dev/null
+++ b/src/types/PollAnswer.zig
@@ -0,0 +1,7 @@
1const Chat = @import("Chat.zig");
2const User = @import("User.zig");
3
4poll_id: []const u8,
5voter_chat: ?Chat,
6user: ?User,
7option_ids: []u64,
diff --git a/src/types/PollOption.zig b/src/types/PollOption.zig
new file mode 100644
index 0000000..870bfee
--- /dev/null
+++ b/src/types/PollOption.zig
@@ -0,0 +1,5 @@
1const MessageEntity = @import("MessageEntity.zig");
2
3text: []const u8,
4text_entities: ?[]MessageEntity = null,
5voter_count: u64,
diff --git a/src/types/PreCheckoutQuery.zig b/src/types/PreCheckoutQuery.zig
new file mode 100644
index 0000000..48e013f
--- /dev/null
+++ b/src/types/PreCheckoutQuery.zig
@@ -0,0 +1,10 @@
1const OrderInfo = @import("OrderInfo.zig");
2const User = @import("User.zig");
3
4id: []const u8,
5from: User,
6currency: []const u8,
7total_amount: u64,
8invoice_payload: []const u8,
9shipping_option_id: ?[]const u8,
10order_info: ?OrderInfo,
diff --git a/src/types/ProximityAlertTriggered.zig b/src/types/ProximityAlertTriggered.zig
new file mode 100644
index 0000000..db5159d
--- /dev/null
+++ b/src/types/ProximityAlertTriggered.zig
@@ -0,0 +1,5 @@
1const User = @import("User.zig");
2
3traveler: User,
4watcher: User,
5distance: u64,
diff --git a/src/types/ReactionCount.zig b/src/types/ReactionCount.zig
new file mode 100644
index 0000000..7c6a0bc
--- /dev/null
+++ b/src/types/ReactionCount.zig
@@ -0,0 +1,4 @@
1const ReactionType = @import("reaction_type.zig").ReactionType;
2
3type: ReactionType,
4total_count: u64,
diff --git a/src/types/RefundedPayment.zig b/src/types/RefundedPayment.zig
new file mode 100644
index 0000000..31f04c0
--- /dev/null
+++ b/src/types/RefundedPayment.zig
@@ -0,0 +1,5 @@
1currency: []const u8,
2total_amount: u64,
3invoice_payload: []const u8,
4telegram_payment_charge_id: []const u8,
5provider_payment_charge_id: []const u8,
diff --git a/src/types/ReplyParameters.zig b/src/types/ReplyParameters.zig
new file mode 100644
index 0000000..57d75c2
--- /dev/null
+++ b/src/types/ReplyParameters.zig
@@ -0,0 +1,9 @@
1message_id: u64,
2// TODO: Integer OR String
3chat_id: ?i64 = null,
4allow_sending_without_reply: ?bool = null,
5quote: ?[]const u8 = null,
6// one of quote_parse_mode or quote_entities
7quote_parse_mode: ?[]const u8 = null,
8quote_entities: ?[]const u8 = null,
9quote_position: ?u64 = null,
diff --git a/src/types/ResponseParameters.zig b/src/types/ResponseParameters.zig
new file mode 100644
index 0000000..655c4f9
--- /dev/null
+++ b/src/types/ResponseParameters.zig
@@ -0,0 +1,2 @@
1migrate_to_chat_id: ?i64 = null,
2retry_after: ?i64 = null,
diff --git a/src/types/SendMessageParams.zig b/src/types/SendMessageParams.zig
new file mode 100644
index 0000000..8c84940
--- /dev/null
+++ b/src/types/SendMessageParams.zig
@@ -0,0 +1,18 @@
1const LinkPreviewOptions = @import("LinkPreviewOptions.zig");
2const MessageEntity = @import("MessageEntity.zig");
3const ParseMode = @import("parse_mode.zig").ParseMode;
4const ReplyParameters = @import("ReplyParameters.zig");
5
6business_connection_id: ?[]const u8 = null,
7// TODO: Integer or String
8chat_id: i64,
9message_thread_id: ?u64 = null,
10text: []const u8,
11parse_mode: ?ParseMode = null,
12entities: ?[]MessageEntity = null,
13link_preview_options: ?LinkPreviewOptions = null,
14disable_notification: ?bool = null,
15protect_content: ?bool = null,
16message_effect_id: ?[]const u8 = null,
17reply_parameters: ?ReplyParameters = null,
18// TODO: reply_markup: InlineKeyboardMarkup OR ReplyKeyboardMarkup OR ReplyKeyboardRemove OR ForceReply
diff --git a/src/types/SetMyNameParams.zig b/src/types/SetMyNameParams.zig
new file mode 100644
index 0000000..0bd2f06
--- /dev/null
+++ b/src/types/SetMyNameParams.zig
@@ -0,0 +1,2 @@
1name: ?[]const u8 = null,
2language_code: ?[]const u8 = null,
diff --git a/src/types/SharedUser.zig b/src/types/SharedUser.zig
new file mode 100644
index 0000000..4afe053
--- /dev/null
+++ b/src/types/SharedUser.zig
@@ -0,0 +1,7 @@
1const PhotoSize = @import("PhotoSize.zig");
2
3user_id: u64,
4first_name: ?[]const u8 = null,
5last_name: ?[]const u8 = null,
6username: ?[]const u8 = null,
7photo: ?[]PhotoSize = null,
diff --git a/src/types/ShippingAddress.zig b/src/types/ShippingAddress.zig
new file mode 100644
index 0000000..f4203fe
--- /dev/null
+++ b/src/types/ShippingAddress.zig
@@ -0,0 +1,6 @@
1country_code: []const u8,
2state: []const u8,
3city: []const u8,
4street_line1: []const u8,
5street_line2: []const u8,
6post_code: []const u8,
diff --git a/src/types/ShippingQuery.zig b/src/types/ShippingQuery.zig
new file mode 100644
index 0000000..15abcda
--- /dev/null
+++ b/src/types/ShippingQuery.zig
@@ -0,0 +1,7 @@
1const ShippingAddress = @import("ShippingAddress.zig");
2const User = @import("User.zig");
3
4id: []const u8,
5from: User,
6invoice_payload: []const u8,
7shipping_address: ShippingAddress,
diff --git a/src/types/Sticker.zig b/src/types/Sticker.zig
new file mode 100644
index 0000000..e28df69
--- /dev/null
+++ b/src/types/Sticker.zig
@@ -0,0 +1,25 @@
1const File = @import("File.zig");
2const MaskPosition = @import("MaskPosition.zig");
3const PhotoSize = @import("PhotoSize.zig");
4
5pub const Type = enum {
6 regular,
7 mask,
8 custom_emoji,
9};
10
11file_id: []const u8,
12file_unique_id: []const u8,
13type: Type,
14width: u64,
15height: u64,
16is_animated: bool = false,
17is_video: bool = false,
18thumbnail: ?PhotoSize = null,
19emoji: ?[]const u8 = null,
20set_name: ?[]const u8 = null,
21premium_animation: ?File = null,
22mask_position: ?MaskPosition = null,
23custom_emoji_id: ?[]const u8 = null,
24needs_repainting: bool = false,
25file_size: ?u64 = null,
diff --git a/src/types/Story.zig b/src/types/Story.zig
new file mode 100644
index 0000000..479b7a9
--- /dev/null
+++ b/src/types/Story.zig
@@ -0,0 +1,4 @@
1const Chat = @import("Chat.zig");
2
3chat: Chat,
4id: u64,
diff --git a/src/types/SuccessfulPayment.zig b/src/types/SuccessfulPayment.zig
new file mode 100644
index 0000000..c508f48
--- /dev/null
+++ b/src/types/SuccessfulPayment.zig
@@ -0,0 +1,9 @@
1const OrderInfo = @import("OrderInfo.zig");
2
3currency: []const u8,
4total_amount: u64,
5invoice_payload: []const u8,
6shipping_option_id: ?[]const u8 = null,
7order_info: ?OrderInfo = null,
8telegram_payment_charge_id: []const u8,
9provider_payment_charge_id: []const u8,
diff --git a/src/types/SwitchInlineQueryChosenChat.zig b/src/types/SwitchInlineQueryChosenChat.zig
new file mode 100644
index 0000000..860f2d9
--- /dev/null
+++ b/src/types/SwitchInlineQueryChosenChat.zig
@@ -0,0 +1,5 @@
1query: ?[]const u8,
2allow_user_chats: ?bool = null,
3allow_bot_chats: ?bool = null,
4allow_group_chats: ?bool = null,
5allow_channel_chats: ?bool = null,
diff --git a/src/types/TextQuote.zig b/src/types/TextQuote.zig
new file mode 100644
index 0000000..6dc6be2
--- /dev/null
+++ b/src/types/TextQuote.zig
@@ -0,0 +1,6 @@
1const MessageEntity = @import("MessageEntity.zig");
2
3text: []const u8,
4entities: ?[]MessageEntity = null,
5position: u64,
6is_manual: bool = false,
diff --git a/src/types/Update.zig b/src/types/Update.zig
new file mode 100644
index 0000000..0491110
--- /dev/null
+++ b/src/types/Update.zig
@@ -0,0 +1,41 @@
1const BusinessConnection = @import("BusinessConnection.zig");
2const BusinessMessagesDeleted = @import("BusinessMessagesDeleted.zig");
3const CallbackQuery = @import("CallbackQuery.zig");
4const ChatBoostRemoved = @import("ChatBoostRemoved.zig");
5const ChatBoostUpdated = @import("ChatBoostUpdated.zig");
6const ChatJoinRequest = @import("ChatJoinRequest.zig");
7const ChatMemberUpdated = @import("ChatMemberUpdated.zig");
8const ChosenInlineResult = @import("ChosenInlineResult.zig");
9const InlineQuery = @import("InlineQuery.zig");
10const Message = @import("Message.zig");
11const MessageReactionCountUpdated = @import("MessageReactionCountUpdated.zig");
12const MessageReactionUpdated = @import("MessageReactionUpdated.zig");
13const Poll = @import("Poll.zig");
14const PollAnswer = @import("PollAnswer.zig");
15const PreCheckoutQuery = @import("PreCheckoutQuery.zig");
16const ShippingQuery = @import("ShippingQuery.zig");
17
18// TODO: Make this into a tagged union
19update_id: u64,
20message: ?Message = null,
21edited_message: ?Message = null,
22channel_post: ?Message = null,
23edited_channel_post: ?Message = null,
24business_connection: ?BusinessConnection = null,
25business_message: ?Message = null,
26edited_business_message: ?Message = null,
27deleted_business_messages: ?BusinessMessagesDeleted = null,
28message_reaction: ?MessageReactionUpdated = null,
29message_reaction_count: ?MessageReactionCountUpdated = null,
30inline_query: ?InlineQuery = null,
31chosen_inline_result: ?ChosenInlineResult = null,
32callback_query: ?CallbackQuery = null,
33shipping_query: ?ShippingQuery = null,
34pre_checkout_query: ?PreCheckoutQuery = null,
35poll: ?Poll = null,
36poll_answer: ?PollAnswer = null,
37my_chat_member: ?ChatMemberUpdated = null,
38chat_member: ?ChatMemberUpdated = null,
39chat_join_request: ?ChatJoinRequest = null,
40chat_boost: ?ChatBoostUpdated = null,
41removed_chat_boost: ?ChatBoostRemoved = null,
diff --git a/src/types/User.zig b/src/types/User.zig
new file mode 100644
index 0000000..dc06097
--- /dev/null
+++ b/src/types/User.zig
@@ -0,0 +1,32 @@
1const textutils = @import("../textutils.zig");
2
3const User = @This();
4
5id: i64,
6is_bot: bool,
7first_name: []const u8,
8last_name: ?[]const u8 = null,
9username: ?[]const u8 = null,
10language_code: ?[]const u8 = null,
11is_premium: bool = false,
12added_to_attachment_menu: bool = false,
13can_join_groups: bool = false,
14can_read_all_group_messages: bool = false,
15supports_inline_queries: bool = false,
16can_connect_to_business: bool = false,
17
18pub fn writeFormattedName(self: User, w: anytype) !void {
19 try w.print("<a href=\"tg://user?id={}\"><i>", .{self.id});
20 try textutils.escapeXml(w, self.first_name);
21 if (self.last_name) |last_name| {
22 try w.writeByte(' ');
23 try textutils.escapeXml(w, last_name);
24 }
25 try w.writeAll("</i>");
26
27 if (self.username) |username| {
28 try w.writeAll(" @");
29 try textutils.escapeXml(w, username);
30 }
31 try w.print("</a> [<code>{}</code>]", .{self.id});
32}
diff --git a/src/types/UsersShared.zig b/src/types/UsersShared.zig
new file mode 100644
index 0000000..0fff785
--- /dev/null
+++ b/src/types/UsersShared.zig
@@ -0,0 +1,4 @@
1const SharedUser = @import("SharedUser.zig");
2
3request_id: u64,
4users: []SharedUser,
diff --git a/src/types/Venue.zig b/src/types/Venue.zig
new file mode 100644
index 0000000..6ae61f3
--- /dev/null
+++ b/src/types/Venue.zig
@@ -0,0 +1,9 @@
1const Location = @import("Location.zig");
2
3location: Location,
4title: []const u8,
5address: []const u8,
6foursquare_id: ?[]const u8 = null,
7foursquare_type: ?[]const u8 = null,
8google_place_id: ?[]const u8 = null,
9google_place_type: ?[]const u8 = null,
diff --git a/src/types/Video.zig b/src/types/Video.zig
new file mode 100644
index 0000000..8330637
--- /dev/null
+++ b/src/types/Video.zig
@@ -0,0 +1,11 @@
1const PhotoSize = @import("PhotoSize.zig");
2
3file_id: []const u8,
4file_unique_id: []const u8,
5width: u64,
6height: u64,
7duration: u64,
8thumbnail: ?PhotoSize = null,
9file_name: ?[]const u8 = null,
10mime_type: ?[]const u8 = null,
11file_size: ?u64 = null,
diff --git a/src/types/VideoChatEnded.zig b/src/types/VideoChatEnded.zig
new file mode 100644
index 0000000..748bf44
--- /dev/null
+++ b/src/types/VideoChatEnded.zig
@@ -0,0 +1 @@
duration: u64,
diff --git a/src/types/VideoChatParticipantsInvited.zig b/src/types/VideoChatParticipantsInvited.zig
new file mode 100644
index 0000000..22a46f7
--- /dev/null
+++ b/src/types/VideoChatParticipantsInvited.zig
@@ -0,0 +1,3 @@
1const User = @import("User.zig");
2
3users: []User,
diff --git a/src/types/VideoChatScheduled.zig b/src/types/VideoChatScheduled.zig
new file mode 100644
index 0000000..a55dfe3
--- /dev/null
+++ b/src/types/VideoChatScheduled.zig
@@ -0,0 +1 @@
start_date: u64,
diff --git a/src/types/VideoChatStarted.zig b/src/types/VideoChatStarted.zig
new file mode 100644
index 0000000..2f72c10
--- /dev/null
+++ b/src/types/VideoChatStarted.zig
@@ -0,0 +1 @@
// meant to be empty
diff --git a/src/types/VideoNote.zig b/src/types/VideoNote.zig
new file mode 100644
index 0000000..aadb318
--- /dev/null
+++ b/src/types/VideoNote.zig
@@ -0,0 +1,8 @@
1const PhotoSize = @import("PhotoSize.zig");
2
3file_id: []const u8,
4file_unique_id: []const u8,
5length: u64,
6duration: u64,
7thumbnail: ?PhotoSize = null,
8file_size: ?u64 = null,
diff --git a/src/types/Voice.zig b/src/types/Voice.zig
new file mode 100644
index 0000000..609ee0b
--- /dev/null
+++ b/src/types/Voice.zig
@@ -0,0 +1,5 @@
1file_id: []const u8,
2file_unique_id: []const u8,
3duration: u64,
4mime_type: ?[]const u8 = null,
5file_size: ?u64 = null,
diff --git a/src/types/WebAppData.zig b/src/types/WebAppData.zig
new file mode 100644
index 0000000..e853fdc
--- /dev/null
+++ b/src/types/WebAppData.zig
@@ -0,0 +1,2 @@
1data: []const u8,
2button_text: []const u8,
diff --git a/src/types/WebAppInfo.zig b/src/types/WebAppInfo.zig
new file mode 100644
index 0000000..02fcc5e
--- /dev/null
+++ b/src/types/WebAppInfo.zig
@@ -0,0 +1 @@
url: []const u8,
diff --git a/src/types/WriteAccessAllowed.zig b/src/types/WriteAccessAllowed.zig
new file mode 100644
index 0000000..2ff3259
--- /dev/null
+++ b/src/types/WriteAccessAllowed.zig
@@ -0,0 +1,3 @@
1from_request: ?bool = null,
2web_app_name: ?[]const u8 = null,
3from_attachment_menu: ?bool = null,
diff --git a/src/types/background_fill.zig b/src/types/background_fill.zig
new file mode 100644
index 0000000..86aaa59
--- /dev/null
+++ b/src/types/background_fill.zig
@@ -0,0 +1,17 @@
1const json = @import("../json.zig");
2
3pub const BackgroundFill = union(enum) {
4 solid: struct {
5 color: u24,
6 },
7 gradient: struct {
8 top_color: u24,
9 bottom_color: u24,
10 rotation_angle: u16,
11 },
12 freeform_gradient: struct {
13 colors: []const u24,
14 },
15
16 pub const jsonParse = json.makeJsonParse(BackgroundFill);
17};
diff --git a/src/types/background_type.zig b/src/types/background_type.zig
new file mode 100644
index 0000000..220465a
--- /dev/null
+++ b/src/types/background_type.zig
@@ -0,0 +1,30 @@
1const json = @import("../json.zig");
2
3const BackgroundFill = @import("background_fill.zig").BackgroundFill;
4const Document = @import("Document.zig");
5
6pub const BackgroundType = union(enum) {
7 fill: struct {
8 fill: BackgroundFill,
9 dark_theme_dimming: u8,
10 },
11 wallpaper: struct {
12 document: Document,
13 dark_theme_dimming: u8,
14 is_blurred: bool = false,
15 is_moving: bool = false,
16 },
17 pattern: struct {
18 document: Document,
19 fill: BackgroundFill,
20 intensity: u8,
21 is_inverted: bool = false,
22 is_moving: bool = false,
23 },
24 chat_theme: struct {
25 theme_name: []const u8,
26 },
27
28 pub const jsonParse = json.makeJsonParse(BackgroundType);
29 pub const jsonParseFromValue = json.makeJsonParseFromValue(BackgroundType);
30};
diff --git a/src/types/chat_boost_source.zig b/src/types/chat_boost_source.zig
new file mode 100644
index 0000000..ead19a0
--- /dev/null
+++ b/src/types/chat_boost_source.zig
@@ -0,0 +1,20 @@
1const json = @import("../json.zig");
2
3const User = @import("User.zig");
4
5pub const ChatBoostSource = union(enum) {
6 premium: struct {
7 user: User,
8 },
9 gift_code: struct {
10 user: User,
11 },
12 giveaway: struct {
13 giveaway_message_id: u64,
14 user: ?User = null,
15 is_unclaimed: bool = false,
16 },
17
18 pub const jsonParse = json.makeJsonParse(ChatBoostSource);
19 pub const jsonParseFromValue = json.makeJsonParseFromValue(ChatBoostSource);
20};
diff --git a/src/types/chat_member.zig b/src/types/chat_member.zig
new file mode 100644
index 0000000..7650299
--- /dev/null
+++ b/src/types/chat_member.zig
@@ -0,0 +1,63 @@
1const json = @import("../json.zig");
2
3const User = @import("User.zig");
4
5pub const ChatMember = union(enum) {
6 creator: struct {
7 user: User,
8 is_anonymous: bool,
9 custom_title: ?[]const u8 = null,
10 },
11 administrator: struct {
12 user: User,
13 can_be_edited: bool,
14 is_anonymous: bool,
15 can_manage_chat: bool,
16 can_delete_messages: bool,
17 can_manage_video_chats: bool,
18 can_restrict_members: bool,
19 can_promote_members: bool,
20 can_change_info: bool,
21 can_invite_users: bool,
22 can_post_stories: bool,
23 can_edit_stories: bool,
24 can_delete_stories: bool,
25 can_post_messages: ?bool = null,
26 can_edit_messages: ?bool = null,
27 can_pin_messages: ?bool = null,
28 can_manage_topics: ?bool = null,
29 custom_title: ?[]const u8 = null,
30 },
31 member: struct {
32 user: User,
33 },
34 restricted: struct {
35 user: User,
36 is_member: bool,
37 can_send_messages: bool,
38 can_send_audios: bool,
39 can_send_documents: bool,
40 can_send_photos: bool,
41 can_send_videos: bool,
42 can_send_video_notes: bool,
43 can_send_voice_notes: bool,
44 can_send_polls: bool,
45 can_send_other_messages: bool,
46 can_add_web_page_previews: bool,
47 can_change_info: bool,
48 can_invite_users: bool,
49 can_pin_messages: bool,
50 can_manage_topics: bool,
51 until_date: u64,
52 },
53 left: struct {
54 user: User,
55 },
56 banned: struct {
57 user: User,
58 until_date: u64,
59 },
60
61 pub const jsonParse = json.makeJsonParse(ChatMember);
62 pub const jsonParseFromValue = json.makeJsonParseFromValueWithTag(ChatMember, "status");
63};
diff --git a/src/types/message_origin.zig b/src/types/message_origin.zig
new file mode 100644
index 0000000..5ca1f9f
--- /dev/null
+++ b/src/types/message_origin.zig
@@ -0,0 +1,29 @@
1const json = @import("../json.zig");
2
3const Chat = @import("Chat.zig");
4const User = @import("User.zig");
5
6pub const MessageOrigin = union(enum) {
7 user: struct {
8 date: u64,
9 sender_user: User,
10 },
11 hidden_user: struct {
12 date: u64,
13 sender_user_name: []const u8,
14 },
15 chat: struct {
16 date: u64,
17 sender_chat: Chat,
18 author_signature: ?[]const u8,
19 },
20 channel: struct {
21 date: u64,
22 chat: Chat,
23 message_id: u64,
24 author_signature: ?[]const u8,
25 },
26
27 pub const jsonParse = json.makeJsonParse(MessageOrigin);
28 pub const jsonParseFromValue = json.makeJsonParseFromValue(MessageOrigin);
29};
diff --git a/src/types/paid_media.zig b/src/types/paid_media.zig
new file mode 100644
index 0000000..3b50250
--- /dev/null
+++ b/src/types/paid_media.zig
@@ -0,0 +1,21 @@
1const json = @import("../json.zig");
2
3const PhotoSize = @import("PhotoSize.zig");
4const Video = @import("Video.zig");
5
6pub const PaidMedia = union(enum) {
7 preview: struct {
8 width: ?u64 = null,
9 height: ?u64 = null,
10 duration: ?u64 = null,
11 },
12 photo: struct {
13 photo: []PhotoSize,
14 },
15 video: struct {
16 video: Video,
17 },
18
19 pub const jsonParse = json.makeJsonParse(PaidMedia);
20 pub const jsonParseFromValue = json.makeJsonParseFromValue(PaidMedia);
21};
diff --git a/src/types/parse_mode.zig b/src/types/parse_mode.zig
new file mode 100644
index 0000000..7285c64
--- /dev/null
+++ b/src/types/parse_mode.zig
@@ -0,0 +1,5 @@
1pub const ParseMode = enum {
2 markdownv2,
3 html,
4 markdown,
5};
diff --git a/src/types/reaction_type.zig b/src/types/reaction_type.zig
new file mode 100644
index 0000000..416def9
--- /dev/null
+++ b/src/types/reaction_type.zig
@@ -0,0 +1,13 @@
1const json = @import("../json.zig");
2
3pub const ReactionType = union(enum) {
4 emoji: struct {
5 emoji: []const u8,
6 },
7 custom_emoji: struct {
8 custom_emoji_id: []const u8,
9 },
10
11 pub const jsonParse = json.makeJsonParse(ReactionType);
12 pub const jsonParseFromValue = json.makeJsonParseFromValue(ReactionType);
13};