mij-discord 1.0.10 → 1.0.11

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4cfa11fc10dd075526964fe8ffaa8519d5979af3
4
- data.tar.gz: '05699e8de50e4d153676da51a365c1cde48f3976'
3
+ metadata.gz: 0ae829acad5e6f041e7f83cca2f109e4d2b844f7
4
+ data.tar.gz: 02c64939be9627ccfb6d6e825e5dbf2f003633c9
5
5
  SHA512:
6
- metadata.gz: 2eccfc34f2bd4db916f72e42177b6be247899500d25f323042ad8272d8abf273b61064ea50c6d85d545f967277c8dc651d842ac4d195907d6259d1593bfbd89a
7
- data.tar.gz: c4532abb901d8a0168423679e2e7a6578289fb8017aedccc36d5380c4a14cbda7a3c568f6c548eb6c639f55a1952e8918afc06cb09638754762b5920db51729d
6
+ metadata.gz: d5a0cbe987ac8756d8948e4693615296c39c149843bfafe415ec0e93ba62474cf164393dfd285f5829254108dd8b7b6ef219b3237a94015d61d787390a554849
7
+ data.tar.gz: b5e2d9ae1edc18597532f9cd94391aa23928424ad92bd5b66f16abdae2ec060286cff356d26f5f124512a1b1cede86ec6fd685d5acde9bad4ec2b90f68a52075
@@ -1,678 +1,688 @@
1
- # frozen_string_literal: true
2
-
3
- module MijDiscord
4
- class Bot
5
- class AuthInfo
6
- attr_reader :id
7
-
8
- attr_reader :token
9
-
10
- attr_reader :type
11
-
12
- attr_reader :name
13
-
14
- def initialize(id, token, type, name)
15
- @id, @type, @name = id.to_id, type, name
16
-
17
- @token = case type
18
- when :bot then "Bot #{token}"
19
- when :user then "#{token}"
20
- else raise ArgumentError, 'Invalid token type'
21
- end
22
- end
23
-
24
- def bot?
25
- @type == :bot
26
- end
27
-
28
- def user?
29
- @type == :user
30
- end
31
-
32
- alias_method :to_s, :token
33
-
34
- def inspect
35
- MijDiscord.make_inspect(self, :id, :type, :name)
36
- end
37
- end
38
-
39
- EVENTS = {
40
- ready: MijDiscord::Events::Ready,
41
- heartbeat: MijDiscord::Events::Heartbeat,
42
- connect: MijDiscord::Events::Connect,
43
- disconnect: MijDiscord::Events::Disconnect,
44
- exception: MijDiscord::Events::Exception,
45
- unhandled: MijDiscord::Events::Unhandled,
46
-
47
- update_user: MijDiscord::Events::UpdateUser,
48
- create_server: MijDiscord::Events::CreateServer,
49
- update_server: MijDiscord::Events::UpdateServer,
50
- delete_server: MijDiscord::Events::DeleteServer,
51
- update_emoji: MijDiscord::Events::UpdateEmoji,
52
- ban_user: MijDiscord::Events::BanUser,
53
- unban_user: MijDiscord::Events::UnbanUser,
54
-
55
- create_role: MijDiscord::Events::CreateRole,
56
- update_role: MijDiscord::Events::UpdateRole,
57
- delete_role: MijDiscord::Events::DeleteRole,
58
- create_member: MijDiscord::Events::CreateMember,
59
- update_member: MijDiscord::Events::UpdateMember,
60
- delete_member: MijDiscord::Events::DeleteMember,
61
-
62
- create_channel: MijDiscord::Events::CreateChannel,
63
- update_channel: MijDiscord::Events::UpdateChannel,
64
- delete_channel: MijDiscord::Events::DeleteChannel,
65
- update_webhooks: MijDiscord::Events::UpdateWebhooks,
66
- update_pins: MijDiscord::Events::UpdatePins,
67
- add_recipient: MijDiscord::Events::AddRecipient,
68
- remove_recipient: MijDiscord::Events::RemoveRecipient,
69
-
70
- create_message: MijDiscord::Events::CreateMessage,
71
- channel_message: MijDiscord::Events::ChannelMessage,
72
- private_message: MijDiscord::Events::PrivateMessage,
73
- edit_message: MijDiscord::Events::EditMessage,
74
- delete_message: MijDiscord::Events::DeleteMessage,
75
- add_reaction: MijDiscord::Events::AddReaction,
76
- remove_reaction: MijDiscord::Events::RemoveReaction,
77
- toggle_reaction: MijDiscord::Events::ToggleReaction,
78
- clear_reactions: MijDiscord::Events::ClearReactions,
79
- start_typing: MijDiscord::Events::StartTyping,
80
-
81
- update_presence: MijDiscord::Events::UpdatePresence,
82
- update_voice_state: MijDiscord::Events::UpdateVoiceState,
83
- }.freeze
84
-
85
- UNAVAILABLE_SERVER_TIMEOUT = 10
86
-
87
- attr_reader :auth
88
-
89
- attr_reader :shard_key
90
-
91
- attr_reader :profile
92
-
93
- attr_reader :gateway
94
-
95
- attr_reader :cache
96
-
97
- def initialize(client_id:, token:, type: :bot, name: nil,
98
- shard_id: nil, num_shards: nil, ignore_bots: false, ignore_self: true)
99
- @auth = AuthInfo.new(client_id, token, type, name)
100
-
101
- @cache = MijDiscord::Cache::BotCache.new(self)
102
-
103
- @shard_key = [shard_id, num_shards] if num_shards
104
- @gateway = MijDiscord::Core::Gateway.new(self, @auth, @shard_key)
105
-
106
- @ignore_bots, @ignore_self, @ignored_ids = ignore_bots, ignore_self, Set.new
107
-
108
- @unavailable_servers = 0
109
-
110
- @event_dispatchers = {}
111
- end
112
-
113
- def connect(async = true)
114
- @gateway.run_async
115
- @gateway.sync unless async
116
- nil
117
- end
118
-
119
- def sync
120
- @gateway.sync
121
- nil
122
- end
123
-
124
- def disconnect(no_sync = false)
125
- @gateway.stop(no_sync)
126
- nil
127
- end
128
-
129
- alias_method :shutdown, :disconnect
130
-
131
- def connected?
132
- @gateway.open?
133
- end
134
-
135
- def servers
136
- gateway_check
137
- @cache.list_servers
138
- end
139
-
140
- def server(id)
141
- gateway_check
142
- @cache.get_server(id)
143
- end
144
-
145
- def channels
146
- gateway_check
147
- @cache.list_channels
148
- end
149
-
150
- def channel(id, server = nil)
151
- gateway_check
152
- @cache.get_channel(id, server)
153
- end
154
-
155
- def pm_channel(id)
156
- gateway_check
157
- @cache.get_pm_channel(id)
158
- end
159
-
160
- alias_method :dm_channel, :pm_channel
161
-
162
- def users
163
- gateway_check
164
- @cache.list_users
165
- end
166
-
167
- def user(id)
168
- gateway_check
169
- @cache.get_user(id)
170
- end
171
-
172
- def members(server_id)
173
- gateway_check
174
- server(server_id)&.members
175
- end
176
-
177
- def member(server_id, id)
178
- gateway_check
179
- server(server_id)&.member(id)
180
- end
181
-
182
- def roles(server_id)
183
- gateway_check
184
- server(server_id)&.roles
185
- end
186
-
187
- def role(server_id, id)
188
- gateway_check
189
- server(server_id)&.role(id)
190
- end
191
-
192
- def emojis(server_id)
193
- gateway_check
194
- server(server_id)&.emojis
195
- end
196
-
197
- def emoji(server_id, id)
198
- gateway_check
199
- server(server_id)&.emoji(id)
200
- end
201
-
202
- def application
203
- raise 'Cannot get OAuth application for non-bot user' unless @auth.bot?
204
-
205
- response = MijDiscord::Core::API.oauth_application(@auth)
206
- MijDiscord::Data::Application.new(JSON.parse(response), self)
207
- end
208
-
209
- def invite(invite)
210
- code = parse_invite_code(invite)
211
- response = MijDiscord::Core::API::Invite.resolve(@auth, code, true)
212
- MijDiscord::Data::Invite.new(JSON.parse(response), self)
213
- end
214
-
215
- def accept_invite(invite)
216
- code = parse_invite_code(invite)
217
- MijDiscord::Core::API::Invite.accept(@auth, code)
218
- nil
219
- end
220
-
221
- def make_invite_url(server: nil, permissions: nil)
222
- url = "https://discordapp.com/oauth2/authorize?scope=bot&client_id=#{@auth.id}".dup
223
- url << "&permissions=#{permissions.to_i}" if permissions.respond_to?(:to_i)
224
- url << "&guild_id=#{server.to_id}" if server.respond_to?(:to_id)
225
- url
226
- end
227
-
228
- def create_server(name, region = 'eu-central')
229
- response = API::Server.create(@auth, name, region)
230
- id = JSON.parse(response)['id'].to_i
231
-
232
- loop do
233
- server = @cache.get_server(id, local: true)
234
- return server if server
235
-
236
- sleep(0.1)
237
- end
238
- end
239
-
240
- def parse_invite_code(invite)
241
- case invite
242
- when %r[^(?:https?://)?discord\.gg/(\w+)$]i then $1
243
- when %r[^https?://discordapp\.com/invite/(\w+)$]i then $1
244
- when %r[^([a-zA-Z0-9]+)$] then $1
245
- when MijDiscord::Data::Invite then invite.code
246
- else raise ArgumentError, 'Invalid invite format'
247
- end
248
- end
249
-
250
- def parse_mention_id(mention, type, server_id = nil)
251
- case type
252
- when :user
253
- return server_id ? member(server_id, mention) : user(mention)
254
-
255
- when :channel
256
- return channel(mention, server_id)
257
-
258
- when :role
259
- role = role(server_id, mention)
260
- return role if role
261
-
262
- servers.each do |sv|
263
- role = sv.role(mention)
264
- return role if role
265
- end
266
-
267
- when :emoji
268
- emoji = emoji(server_id, mention)
269
- return emoji if emoji
270
-
271
- servers.each do |sv|
272
- emoji = sv.emoji(mention)
273
- return emoji if emoji
274
- end
275
-
276
- else raise TypeError, "Invalid mention type '#{type}'"
277
- end
278
-
279
- nil
280
- end
281
-
282
- def parse_mention(mention, server_id = nil, type: nil)
283
- gateway_check
284
-
285
- mention = mention.to_s.strip
286
-
287
- if !type.nil? && mention =~ /^(\d+)$/
288
- parse_mention_id($1, type, server_id)
289
-
290
- elsif mention =~ /^<@!?(\d+)>$/
291
- return nil if type && type != :user
292
- parse_mention_id($1, :user, server_id)
293
-
294
- elsif mention =~ /^<#(\d+)>$/
295
- return nil if type && type != :channel
296
- parse_mention_id($1, :channel, server_id)
297
-
298
- elsif mention =~ /^<@&(\d+)>$/
299
- return nil if type && type != :role
300
- parse_mention_id($1, :role, server_id)
301
-
302
- elsif mention =~ /^<(a?):(\w+):(\d+)>$/
303
- return nil if type && type != :emoji
304
- parse_mention_id($1, :emoji, server_id) || begin
305
- em_data = { 'id' => $3.to_i, 'name' => $2, 'animated' => !$1.empty? }
306
- MijDiscord::Data::Emoji.new(em_data, nil)
307
- end
308
-
309
- end
310
- end
311
-
312
- def add_event(type, key = nil, **filter, &block)
313
- raise ArgumentError, "Invalid event type: #{type}" unless EVENTS[type]
314
-
315
- event = (@event_dispatchers[type] ||= MijDiscord::Events::EventDispatcher.new(EVENTS[type], self))
316
- event.add_callback(key, filter, &block)
317
- end
318
-
319
- def remove_event(type, key)
320
- raise ArgumentError, "Invalid event type: #{type}" unless EVENTS[type]
321
-
322
- @event_dispatchers[type]&.remove_callback(key)
323
- nil
324
- end
325
-
326
- def events(type)
327
- raise ArgumentError, "Invalid event type: #{type}" unless EVENTS[type]
328
-
329
- @event_dispatchers[type]&.callbacks || []
330
- end
331
-
332
- def ignore_user(user)
333
- @ignored_ids << user.to_id
334
- nil
335
- end
336
-
337
- def unignore_user(user)
338
- @ignored_ids.delete(user.to_id)
339
- nil
340
- end
341
-
342
- def ignored_user?(user)
343
- user = user.to_id
344
-
345
- return true if @ignore_self && user == @auth.id
346
- return true if @ignored_ids.include?(user)
347
-
348
- if @ignore_bots && (user = @cache.get_user(user, local: true))
349
- return true if user.bot_account?
350
- end
351
-
352
- false
353
- end
354
-
355
- def update_presence(status: nil, game: nil)
356
- gateway_check
357
-
358
- status = case status
359
- when nil then @profile.status
360
- when :online, :idle, :dnd, :online then status
361
- else raise ArgumentError, 'Invalid status'
362
- end
363
-
364
- game = case game
365
- when nil then @profile.game
366
- when false then nil
367
- when String, Hash
368
- MijDiscord::Data::Game.construct(game)
369
- when MijDiscord::Data::Game then game
370
- else raise ArgumentError, 'Invalid game'
371
- end&.to_hash
372
-
373
- @gateway.send_status_update(status, nil, game, false)
374
- @profile.update_presence('status' => status, 'game' => game)
375
- nil
376
- end
377
-
378
- def handle_heartbeat
379
- trigger_event(:heartbeat, self)
380
- end
381
-
382
- def handle_exception(type, exception, payload = nil)
383
- return if type == :event && payload&.is_a?(MijDiscord::Events::Exception)
384
-
385
- trigger_event(:exception, self, type, exception, payload)
386
- end
387
-
388
- def handle_dispatch(type, data)
389
- MijDiscord::LOGGER.debug('Dispatch') { "<#{type} #{data.inspect}>" }
390
-
391
- if @unavailable_servers > 0 && Time.now > @unavailable_servers_timeout
392
- MijDiscord::LOGGER.warn('Dispatch') { "Proceeding with #{@unavailable_servers} servers still unavailable" }
393
-
394
- @unavailable_servers = 0
395
- notify_ready
396
- end
397
-
398
- case type
399
- when :CONNECT
400
- trigger_event(:connect, self)
401
-
402
- when :DISCONNECT
403
- trigger_event(:disconnect, self)
404
-
405
- when :READY
406
- @cache.reset
407
-
408
- @profile = MijDiscord::Data::Profile.new(data['user'], self)
409
- @profile.update_presence('status' => :online)
410
-
411
- @unavailable_servers = 0
412
- @unavailable_servers_timeout = Time.now + UNAVAILABLE_SERVER_TIMEOUT
413
-
414
- data['guilds'].each do |sv|
415
- if sv['unavailable'].eql?(true)
416
- @unavailable_servers += 1
417
- else
418
- @cache.put_server(sv)
419
- end
420
- end
421
-
422
- data['private_channels'].each do |ch|
423
- @cache.put_channel(ch, nil)
424
- end
425
-
426
- notify_ready if @unavailable_servers.zero?
427
-
428
- when :SESSIONS_REPLACE
429
- # Do nothing with session replace because no idea what it does.
430
-
431
- when :PRESENCES_REPLACE
432
- # Do nothing with presences replace because no idea what it does.
433
-
434
- when :GUILD_MEMBERS_CHUNK
435
- server = @cache.get_server(data['guild_id'])
436
- server.update_members_chunk(data['members'])
437
-
438
- when :GUILD_CREATE
439
- server = @cache.put_server(data)
440
-
441
- if data['unavailable'].eql?(false)
442
- @unavailable_servers -= 1
443
- @unavailable_servers_timeout = Time.now + UNAVAILABLE_SERVER_TIMEOUT
444
-
445
- notify_ready if @unavailable_servers.zero?
446
- return
447
- end
448
-
449
- trigger_event(:create_server, self, server)
450
-
451
- when :GUILD_SYNC
452
- server = @cache.get_server(data['id'])
453
- server.update_synced_data(data)
454
-
455
- when :GUILD_UPDATE
456
- server = @cache.put_server(data, update: true)
457
- trigger_event(:update_server, self, server)
458
-
459
- when :GUILD_DELETE
460
- server = @cache.remove_server(data['id'])
461
-
462
- if data['unavailable'].eql?(true)
463
- MijDiscord::LOGGER.warn('Dispatch') { "Server <#{data['id']}> died due to outage" }
464
- return
465
- end
466
-
467
- trigger_event(:delete_server, self, server)
468
-
469
- when :CHANNEL_CREATE
470
- channel = @cache.put_channel(data, nil)
471
- trigger_event(:create_channel, self, channel)
472
-
473
- when :CHANNEL_UPDATE
474
- channel = @cache.put_channel(data, nil, update: true)
475
- trigger_event(:update_channel, self, channel)
476
-
477
- when :CHANNEL_DELETE
478
- channel = @cache.remove_channel(data['id'])
479
- trigger_event(:delete_channel, self, channel)
480
-
481
- when :WEBHOOKS_UPDATE
482
- channel = @cache.get_channel(data['channel_id'], nil)
483
- trigger_event(:update_webhooks, self, channel)
484
-
485
- when :CHANNEL_PINS_UPDATE
486
- channel = @cache.get_channel(data['channel_id'], nil)
487
- trigger_event(:update_pins, self, channel)
488
-
489
- when :CHANNEL_PINS_ACK
490
- # Do nothing with pins acknowledgement
491
-
492
- when :CHANNEL_RECIPIENT_ADD
493
- channel = @cache.get_channel(data['channel_id'], nil)
494
- recipient = channel.update_recipient(add: data['user'])
495
- trigger_event(:add_recipient, self, channel, recipient)
496
-
497
- when :CHANNEL_RECIPIENT_REMOVE
498
- channel = @cache.get_channel(data['channel_id'], nil)
499
- recipient = channel.update_recipient(remove: data['user'])
500
- trigger_event(:remove_recipient, self, channel, recipient)
501
-
502
- when :GUILD_MEMBER_ADD
503
- server = @cache.get_server(data['guild_id'])
504
- member = server.update_member(data, :add)
505
- trigger_event(:create_member, self, member, server)
506
-
507
- when :GUILD_MEMBER_UPDATE
508
- server = @cache.get_server(data['guild_id'])
509
- member = server.update_member(data, :update)
510
- trigger_event(:update_member, self, member, server)
511
-
512
- when :GUILD_MEMBER_REMOVE
513
- server = @cache.get_server(data['guild_id'])
514
- member = server.update_member(data, :remove)
515
- trigger_event(:delete_member, self, member, server)
516
-
517
- when :GUILD_ROLE_CREATE
518
- server = @cache.get_server(data['guild_id'])
519
- role = server.cache.put_role(data['role'])
520
- trigger_event(:create_role, self, server, role)
521
-
522
- when :GUILD_ROLE_UPDATE
523
- server = @cache.get_server(data['guild_id'])
524
- role = server.cache.put_role(data['role'], update: true)
525
- trigger_event(:update_role, self, server, role)
526
-
527
- when :GUILD_ROLE_DELETE
528
- server = @cache.get_server(data['guild_id'])
529
- role = server.cache.remove_role(data['role_id'])
530
- trigger_event(:delete_role, self, server, role)
531
-
532
- when :GUILD_EMOJIS_UPDATE
533
- server = @cache.get_server(data['guild_id'])
534
- old_emojis = server.emojis
535
- server.update_emojis(data)
536
- trigger_event(:update_emoji, self, server, old_emojis, server.emojis)
537
-
538
- when :GUILD_BAN_ADD
539
- server = @cache.get_server(data['guild_id'])
540
- user = @cache.get_user(data['user']['id'], local: @auth.user?)
541
- user ||= MijDiscord::Data::User.new(data['user'], self)
542
- trigger_event(:ban_user, self, server, user)
543
-
544
- when :GUILD_BAN_REMOVE
545
- server = @cache.get_server(data['guild_id'])
546
- user = @cache.get_user(data['user']['id'], local: @auth.user?)
547
- user ||= MijDiscord::Data::User.new(data['user'], self)
548
- trigger_event(:unban_user, self, server, user)
549
-
550
- when :MESSAGE_CREATE
551
- channel = @cache.get_channel(data['channel_id'], nil)
552
- message = channel.cache.put_message(data)
553
-
554
- return if ignored_user?(data['author']['id'])
555
- trigger_event(:create_message, self, message)
556
-
557
- if message.channel.private?
558
- trigger_event(:private_message, self, message)
559
- else
560
- trigger_event(:channel_message, self, message)
561
- end
562
-
563
- when :MESSAGE_ACK
564
- # Do nothing with message acknowledgement
565
-
566
- when :MESSAGE_UPDATE
567
- author = data['author']
568
- return if author.nil?
569
-
570
- channel = @cache.get_channel(data['channel_id'], nil)
571
- message = channel.cache.put_message(data, update: true)
572
-
573
- return if ignored_user?(author['id'])
574
- trigger_event(:edit_message, self, message)
575
-
576
- when :MESSAGE_DELETE
577
- channel = @cache.get_channel(data['channel_id'], nil)
578
- channel.cache.remove_message(data['id'])
579
-
580
- trigger_event(:delete_message, self, data)
581
-
582
- when :MESSAGE_DELETE_BULK
583
- messages = data['ids'].map {|x| {'id' => x, 'channel_id' => data['channel_id']} }
584
- messages.each {|x| trigger_event(:delete_message, self, x) }
585
-
586
- when :MESSAGE_REACTION_ADD
587
- channel = @cache.get_channel(data['channel_id'], nil)
588
- message = channel.cache.get_message(data['message_id'], local: true)
589
- message.update_reaction(add: data) if message
590
-
591
- return if ignored_user?(data['user_id'])
592
- trigger_event(:add_reaction, self, data)
593
- trigger_event(:toggle_reaction, self, data)
594
-
595
- when :MESSAGE_REACTION_REMOVE
596
- channel = @cache.get_channel(data['channel_id'], nil)
597
- message = channel.cache.get_message(data['message_id'], local: true)
598
- message.update_reaction(remove: data) if message
599
-
600
- return if ignored_user?(data['user_id'])
601
- trigger_event(:remove_reaction, self, data)
602
- trigger_event(:toggle_reaction, self, data)
603
-
604
- when :MESSAGE_REACTION_REMOVE_ALL
605
- channel = @cache.get_channel(data['channel_id'], nil)
606
- message = channel.cache.get_message(data['message_id'], local: true)
607
- message.update_reaction(clear: true) if message
608
-
609
- trigger_event(:clear_reactions, self, data)
610
-
611
- when :TYPING_START
612
- begin
613
- return if ignored_user?(data['user_id'])
614
- trigger_event(:start_typing, self, data)
615
- rescue MijDiscord::Errors::Forbidden
616
- # Ignoring the channel we can't access
617
- # Why is this even sent? :S
618
- end
619
-
620
- when :USER_UPDATE
621
- user = @cache.put_user(data, update: true)
622
- @profile.update_data(data) if @profile == user
623
-
624
- trigger_event(:update_user, self, user)
625
-
626
- when :PRESENCE_UPDATE
627
- if data['guild_id']
628
- server = @cache.get_server(data['guild_id'])
629
- user = server.cache.get_member(data['user']['id'])
630
- user&.update_presence(data)
631
- else
632
- user = @cache.get_user(data['user']['id'])
633
- user&.update_presence(data)
634
- @profile.update_presence(data) if @profile == user
635
- end
636
-
637
- trigger_event(:update_presence, self, data)
638
-
639
- when :VOICE_STATE_UPDATE
640
- server = @cache.get_server(data['guild_id'])
641
- state = server.update_voice_state(data)
642
- trigger_event(:update_voice_state, self, state)
643
-
644
- else
645
- MijDiscord::LOGGER.warn('Dispatch') { "Unhandled gateway event type: #{type}" }
646
- trigger_event(:unhandled, self, type, data)
647
- end
648
- rescue => exc
649
- MijDiscord::LOGGER.error('Dispatch') { 'An error occurred in dispatch handler' }
650
- MijDiscord::LOGGER.error('Dispatch') { exc }
651
- end
652
-
653
- def inspect
654
- MijDiscord.make_inspect(self, :auth)
655
- end
656
-
657
- private
658
-
659
- def gateway_check
660
- raise 'A gateway connection is required for this action' unless connected?
661
- end
662
-
663
- def notify_ready
664
- @gateway.notify_ready
665
-
666
- trigger_event(:ready, self)
667
-
668
- if @auth.user?
669
- guilds = @cache.list_servers.map(&:id)
670
- @gateway.send_request_guild_sync(guilds)
671
- end
672
- end
673
-
674
- def trigger_event(name, *args)
675
- @event_dispatchers[name]&.trigger(args)
676
- end
677
- end
1
+ # frozen_string_literal: true
2
+
3
+ module MijDiscord
4
+ class Bot
5
+ class AuthInfo
6
+ attr_reader :id
7
+
8
+ attr_reader :token
9
+
10
+ attr_reader :type
11
+
12
+ attr_reader :name
13
+
14
+ def initialize(id, token, type, name)
15
+ @id, @type, @name = id.to_id, type, name
16
+
17
+ @token = case type
18
+ when :bot then "Bot #{token}"
19
+ when :user then "#{token}"
20
+ else raise ArgumentError, 'Invalid token type'
21
+ end
22
+ end
23
+
24
+ def bot?
25
+ @type == :bot
26
+ end
27
+
28
+ def user?
29
+ @type == :user
30
+ end
31
+
32
+ alias_method :to_s, :token
33
+
34
+ def inspect
35
+ MijDiscord.make_inspect(self, :id, :type, :name)
36
+ end
37
+ end
38
+
39
+ EVENTS = {
40
+ ready: MijDiscord::Events::Ready,
41
+ heartbeat: MijDiscord::Events::Heartbeat,
42
+ connect: MijDiscord::Events::Connect,
43
+ disconnect: MijDiscord::Events::Disconnect,
44
+ exception: MijDiscord::Events::Exception,
45
+ unhandled: MijDiscord::Events::Unhandled,
46
+
47
+ update_user: MijDiscord::Events::UpdateUser,
48
+ create_server: MijDiscord::Events::CreateServer,
49
+ update_server: MijDiscord::Events::UpdateServer,
50
+ delete_server: MijDiscord::Events::DeleteServer,
51
+ update_emoji: MijDiscord::Events::UpdateEmoji,
52
+ ban_user: MijDiscord::Events::BanUser,
53
+ unban_user: MijDiscord::Events::UnbanUser,
54
+
55
+ create_role: MijDiscord::Events::CreateRole,
56
+ update_role: MijDiscord::Events::UpdateRole,
57
+ delete_role: MijDiscord::Events::DeleteRole,
58
+ create_member: MijDiscord::Events::CreateMember,
59
+ update_member: MijDiscord::Events::UpdateMember,
60
+ delete_member: MijDiscord::Events::DeleteMember,
61
+
62
+ create_channel: MijDiscord::Events::CreateChannel,
63
+ update_channel: MijDiscord::Events::UpdateChannel,
64
+ delete_channel: MijDiscord::Events::DeleteChannel,
65
+ update_webhooks: MijDiscord::Events::UpdateWebhooks,
66
+ update_pins: MijDiscord::Events::UpdatePins,
67
+ add_recipient: MijDiscord::Events::AddRecipient,
68
+ remove_recipient: MijDiscord::Events::RemoveRecipient,
69
+
70
+ create_message: MijDiscord::Events::CreateMessage,
71
+ channel_message: MijDiscord::Events::ChannelMessage,
72
+ private_message: MijDiscord::Events::PrivateMessage,
73
+ edit_message: MijDiscord::Events::EditMessage,
74
+ delete_message: MijDiscord::Events::DeleteMessage,
75
+ add_reaction: MijDiscord::Events::AddReaction,
76
+ remove_reaction: MijDiscord::Events::RemoveReaction,
77
+ toggle_reaction: MijDiscord::Events::ToggleReaction,
78
+ clear_reactions: MijDiscord::Events::ClearReactions,
79
+ start_typing: MijDiscord::Events::StartTyping,
80
+
81
+ update_presence: MijDiscord::Events::UpdatePresence,
82
+ update_voice_state: MijDiscord::Events::UpdateVoiceState,
83
+ }.freeze
84
+
85
+ UNAVAILABLE_SERVER_TIMEOUT = 10
86
+
87
+ attr_reader :auth
88
+
89
+ attr_reader :shard_key
90
+
91
+ attr_reader :profile
92
+
93
+ attr_reader :gateway
94
+
95
+ attr_reader :cache
96
+
97
+ def initialize(client_id:, token:, type: :bot, name: nil,
98
+ shard_id: nil, num_shards: nil, ignore_bots: false, ignore_self: true)
99
+ @auth = AuthInfo.new(client_id, token, type, name)
100
+
101
+ @cache = MijDiscord::Cache::BotCache.new(self)
102
+
103
+ @shard_key = [shard_id, num_shards] if num_shards
104
+ @gateway = MijDiscord::Core::Gateway.new(self, @auth, @shard_key)
105
+
106
+ @ignore_bots, @ignore_self, @ignored_ids = ignore_bots, ignore_self, Set.new
107
+
108
+ @unavailable_servers = 0
109
+
110
+ @event_dispatchers = {}
111
+ end
112
+
113
+ def connect(async = true)
114
+ @gateway.run_async
115
+ @gateway.sync unless async
116
+ nil
117
+ end
118
+
119
+ def sync
120
+ @gateway.sync
121
+ nil
122
+ end
123
+
124
+ def disconnect(no_sync = false)
125
+ @gateway.stop(no_sync)
126
+ nil
127
+ end
128
+
129
+ alias_method :shutdown, :disconnect
130
+
131
+ def connected?
132
+ @gateway.open?
133
+ end
134
+
135
+ def servers
136
+ gateway_check
137
+ @cache.list_servers
138
+ end
139
+
140
+ def server(id)
141
+ gateway_check
142
+ @cache.get_server(id)
143
+ end
144
+
145
+ def channels
146
+ gateway_check
147
+ @cache.list_channels
148
+ end
149
+
150
+ def channel(id, server = nil)
151
+ gateway_check
152
+ @cache.get_channel(id, server)
153
+ end
154
+
155
+ def pm_channel(id)
156
+ gateway_check
157
+ @cache.get_pm_channel(id)
158
+ end
159
+
160
+ alias_method :dm_channel, :pm_channel
161
+
162
+ def users
163
+ gateway_check
164
+ @cache.list_users
165
+ end
166
+
167
+ def user(id)
168
+ gateway_check
169
+ @cache.get_user(id)
170
+ end
171
+
172
+ def members(server_id)
173
+ gateway_check
174
+ server(server_id)&.members
175
+ end
176
+
177
+ def member(server_id, id)
178
+ gateway_check
179
+ server(server_id)&.member(id)
180
+ end
181
+
182
+ def roles(server_id)
183
+ gateway_check
184
+ server(server_id)&.roles
185
+ end
186
+
187
+ def role(server_id, id)
188
+ gateway_check
189
+ server(server_id)&.role(id)
190
+ end
191
+
192
+ def emojis(server_id)
193
+ gateway_check
194
+ server(server_id)&.emojis
195
+ end
196
+
197
+ def emoji(server_id, id)
198
+ gateway_check
199
+ server(server_id)&.emoji(id)
200
+ end
201
+
202
+ def application
203
+ raise 'Cannot get OAuth application for non-bot user' unless @auth.bot?
204
+
205
+ response = MijDiscord::Core::API.oauth_application(@auth)
206
+ MijDiscord::Data::Application.new(JSON.parse(response), self)
207
+ end
208
+
209
+ def invite(invite)
210
+ code = parse_invite_code(invite)
211
+ response = MijDiscord::Core::API::Invite.resolve(@auth, code, true)
212
+ MijDiscord::Data::Invite.new(JSON.parse(response), self)
213
+ end
214
+
215
+ def accept_invite(invite)
216
+ code = parse_invite_code(invite)
217
+ MijDiscord::Core::API::Invite.accept(@auth, code)
218
+ nil
219
+ end
220
+
221
+ def make_invite_url(server: nil, permissions: nil)
222
+ url = "https://discordapp.com/oauth2/authorize?scope=bot&client_id=#{@auth.id}".dup
223
+ url << "&permissions=#{permissions.to_i}" if permissions.respond_to?(:to_i)
224
+ url << "&guild_id=#{server.to_id}" if server.respond_to?(:to_id)
225
+ url
226
+ end
227
+
228
+ def create_server(name, region = 'eu-central')
229
+ response = API::Server.create(@auth, name, region)
230
+ id = JSON.parse(response)['id'].to_i
231
+
232
+ loop do
233
+ server = @cache.get_server(id, local: true)
234
+ return server if server
235
+
236
+ sleep(0.1)
237
+ end
238
+ end
239
+
240
+ def parse_invite_code(invite)
241
+ case invite
242
+ when %r[^(?:https?://)?discord\.gg/(\w+)$]i then $1
243
+ when %r[^https?://discordapp\.com/invite/(\w+)$]i then $1
244
+ when %r[^([a-zA-Z0-9]+)$] then $1
245
+ when MijDiscord::Data::Invite then invite.code
246
+ else raise ArgumentError, 'Invalid invite format'
247
+ end
248
+ end
249
+
250
+ def parse_mention_id(mention, type, server_id = nil)
251
+ case type
252
+ when :user
253
+ return server_id ? member(server_id, mention) : user(mention)
254
+
255
+ when :channel
256
+ return channel(mention, server_id)
257
+
258
+ when :role
259
+ role = role(server_id, mention)
260
+ return role if role
261
+
262
+ servers.each do |sv|
263
+ role = sv.role(mention)
264
+ return role if role
265
+ end
266
+
267
+ when :emoji
268
+ emoji = emoji(server_id, mention)
269
+ return emoji if emoji
270
+
271
+ servers.each do |sv|
272
+ emoji = sv.emoji(mention)
273
+ return emoji if emoji
274
+ end
275
+
276
+ else raise TypeError, "Invalid mention type '#{type}'"
277
+ end
278
+
279
+ nil
280
+ end
281
+
282
+ def parse_mention(mention, server_id = nil, type: nil)
283
+ gateway_check
284
+
285
+ mention = mention.to_s.strip
286
+
287
+ if !type.nil? && mention =~ /^(\d+)$/
288
+ parse_mention_id($1, type, server_id)
289
+
290
+ elsif mention =~ /^<@!?(\d+)>$/
291
+ return nil if type && type != :user
292
+ parse_mention_id($1, :user, server_id)
293
+
294
+ elsif mention =~ /^<#(\d+)>$/
295
+ return nil if type && type != :channel
296
+ parse_mention_id($1, :channel, server_id)
297
+
298
+ elsif mention =~ /^<@&(\d+)>$/
299
+ return nil if type && type != :role
300
+ parse_mention_id($1, :role, server_id)
301
+
302
+ elsif mention =~ /^<(a?):(\w+):(\d+)>$/
303
+ return nil if type && type != :emoji
304
+ parse_mention_id($1, :emoji, server_id) || begin
305
+ em_data = { 'id' => $3.to_i, 'name' => $2, 'animated' => !$1.empty? }
306
+ MijDiscord::Data::Emoji.new(em_data, nil)
307
+ end
308
+
309
+ end
310
+ end
311
+
312
+ def add_event(type, key = nil, **filter, &block)
313
+ raise ArgumentError, "Invalid event type: #{type}" unless EVENTS[type]
314
+
315
+ event = (@event_dispatchers[type] ||= MijDiscord::Events::EventDispatcher.new(EVENTS[type], self))
316
+ event.add_callback(key, filter, &block)
317
+ end
318
+
319
+ def remove_event(type, key)
320
+ raise ArgumentError, "Invalid event type: #{type}" unless EVENTS[type]
321
+
322
+ @event_dispatchers[type]&.remove_callback(key)
323
+ nil
324
+ end
325
+
326
+ def events(type)
327
+ raise ArgumentError, "Invalid event type: #{type}" unless EVENTS[type]
328
+
329
+ @event_dispatchers[type]&.callbacks || []
330
+ end
331
+
332
+ def ignore_user(user)
333
+ @ignored_ids << user.to_id
334
+ nil
335
+ end
336
+
337
+ def unignore_user(user)
338
+ @ignored_ids.delete(user.to_id)
339
+ nil
340
+ end
341
+
342
+ def ignored_user?(user)
343
+ user = user.to_id
344
+
345
+ return true if @ignore_self && user == @auth.id
346
+ return true if @ignored_ids.include?(user)
347
+
348
+ if @ignore_bots && (user = @cache.get_user(user, local: true))
349
+ return true if user.bot_account?
350
+ end
351
+
352
+ false
353
+ end
354
+
355
+ def update_presence(status: nil, game: nil)
356
+ gateway_check
357
+
358
+ status = case status
359
+ when nil then @profile.status
360
+ when :online, :idle, :dnd, :online then status
361
+ else raise ArgumentError, 'Invalid status'
362
+ end
363
+
364
+ game = case game
365
+ when nil then @profile.game
366
+ when false then nil
367
+ when String, Hash
368
+ MijDiscord::Data::Game.construct(game)
369
+ when MijDiscord::Data::Game then game
370
+ else raise ArgumentError, 'Invalid game'
371
+ end&.to_hash
372
+
373
+ @gateway.send_status_update(status, nil, game, false)
374
+ @profile.update_presence('status' => status, 'game' => game)
375
+ nil
376
+ end
377
+
378
+ def handle_heartbeat
379
+ trigger_event(:heartbeat, self)
380
+ end
381
+
382
+ def handle_exception(type, exception, payload = nil)
383
+ return if type == :event && payload&.is_a?(MijDiscord::Events::Exception)
384
+
385
+ trigger_event(:exception, self, type, exception, payload)
386
+ end
387
+
388
+ def handle_dispatch(type, data)
389
+ MijDiscord::LOGGER.debug('Dispatch') { "<#{type} #{data.inspect}>" }
390
+
391
+ if @unavailable_servers > 0 && Time.now > @unavailable_servers_timeout
392
+ MijDiscord::LOGGER.warn('Dispatch') { "Proceeding with #{@unavailable_servers} servers still unavailable" }
393
+
394
+ @unavailable_servers = 0
395
+ notify_ready
396
+ end
397
+
398
+ case type
399
+ when :CONNECT
400
+ trigger_event(:connect, self)
401
+
402
+ when :DISCONNECT
403
+ trigger_event(:disconnect, self)
404
+
405
+ when :READY
406
+ @cache.reset
407
+
408
+ @profile = MijDiscord::Data::Profile.new(data['user'], self)
409
+ @profile.update_presence('status' => :online)
410
+
411
+ @unavailable_servers = 0
412
+ @unavailable_servers_timeout = Time.now + UNAVAILABLE_SERVER_TIMEOUT
413
+
414
+ data['guilds'].each do |sv|
415
+ if sv['unavailable'].eql?(true)
416
+ @unavailable_servers += 1
417
+ else
418
+ @cache.put_server(sv)
419
+ end
420
+ end
421
+
422
+ data['private_channels'].each do |ch|
423
+ @cache.put_channel(ch, nil)
424
+ end
425
+
426
+ notify_ready if @unavailable_servers.zero?
427
+
428
+ when :SESSIONS_REPLACE
429
+ # Do nothing with session replace because no idea what it does.
430
+
431
+ when :PRESENCES_REPLACE
432
+ # Do nothing with presences replace because no idea what it does.
433
+
434
+ when :GUILD_MEMBERS_CHUNK
435
+ server = @cache.get_server(data['guild_id'])
436
+ server.update_members_chunk(data['members'])
437
+
438
+ when :GUILD_CREATE
439
+ server = @cache.put_server(data)
440
+
441
+ if data['unavailable'].eql?(false)
442
+ @unavailable_servers -= 1
443
+ @unavailable_servers_timeout = Time.now + UNAVAILABLE_SERVER_TIMEOUT
444
+
445
+ notify_ready if @unavailable_servers.zero?
446
+ return
447
+ end
448
+
449
+ trigger_event(:create_server, self, server)
450
+
451
+ when :GUILD_SYNC
452
+ server = @cache.get_server(data['id'])
453
+ server.update_synced_data(data)
454
+
455
+ when :GUILD_UPDATE
456
+ server = @cache.put_server(data, update: true)
457
+ trigger_event(:update_server, self, server)
458
+
459
+ when :GUILD_DELETE
460
+ server = @cache.remove_server(data['id'])
461
+
462
+ if data['unavailable'].eql?(true)
463
+ MijDiscord::LOGGER.warn('Dispatch') { "Server <#{data['id']}> died due to outage" }
464
+ return
465
+ end
466
+
467
+ trigger_event(:delete_server, self, server)
468
+
469
+ when :CHANNEL_CREATE
470
+ channel = @cache.put_channel(data, nil)
471
+ trigger_event(:create_channel, self, channel)
472
+
473
+ when :CHANNEL_UPDATE
474
+ channel = @cache.put_channel(data, nil, update: true)
475
+ trigger_event(:update_channel, self, channel)
476
+
477
+ when :CHANNEL_DELETE
478
+ channel = @cache.remove_channel(data['id'])
479
+ trigger_event(:delete_channel, self, channel)
480
+
481
+ when :WEBHOOKS_UPDATE
482
+ channel = @cache.get_channel(data['channel_id'], nil)
483
+ trigger_event(:update_webhooks, self, channel)
484
+
485
+ when :CHANNEL_PINS_UPDATE
486
+ channel = @cache.get_channel(data['channel_id'], nil)
487
+ trigger_event(:update_pins, self, channel)
488
+
489
+ when :CHANNEL_PINS_ACK
490
+ # Do nothing with pins acknowledgement
491
+
492
+ when :CHANNEL_RECIPIENT_ADD
493
+ channel = @cache.get_channel(data['channel_id'], nil)
494
+ recipient = channel.update_recipient(add: data['user'])
495
+ trigger_event(:add_recipient, self, channel, recipient)
496
+
497
+ when :CHANNEL_RECIPIENT_REMOVE
498
+ channel = @cache.get_channel(data['channel_id'], nil)
499
+ recipient = channel.update_recipient(remove: data['user'])
500
+ trigger_event(:remove_recipient, self, channel, recipient)
501
+
502
+ when :GUILD_MEMBER_ADD
503
+ server = @cache.get_server(data['guild_id'])
504
+ member = server.update_member(data, :add)
505
+ trigger_event(:create_member, self, member, server)
506
+
507
+ when :GUILD_MEMBER_UPDATE
508
+ server = @cache.get_server(data['guild_id'])
509
+ member = server.update_member(data, :update)
510
+ trigger_event(:update_member, self, member, server)
511
+
512
+ when :GUILD_MEMBER_REMOVE
513
+ server = @cache.get_server(data['guild_id'])
514
+ member = server.update_member(data, :remove)
515
+ trigger_event(:delete_member, self, member, server)
516
+
517
+ when :GUILD_ROLE_CREATE
518
+ server = @cache.get_server(data['guild_id'])
519
+ role = server.cache.put_role(data['role'])
520
+ trigger_event(:create_role, self, server, role)
521
+
522
+ when :GUILD_ROLE_UPDATE
523
+ server = @cache.get_server(data['guild_id'])
524
+ role = server.cache.put_role(data['role'], update: true)
525
+ trigger_event(:update_role, self, server, role)
526
+
527
+ when :GUILD_ROLE_DELETE
528
+ server = @cache.get_server(data['guild_id'])
529
+ role = server.cache.remove_role(data['role_id'])
530
+ trigger_event(:delete_role, self, server, role)
531
+
532
+ when :GUILD_EMOJIS_UPDATE
533
+ server = @cache.get_server(data['guild_id'])
534
+ old_emojis = server.emojis
535
+ server.update_emojis(data)
536
+ trigger_event(:update_emoji, self, server, old_emojis, server.emojis)
537
+
538
+ when :GUILD_BAN_ADD
539
+ server = @cache.get_server(data['guild_id'])
540
+ user = @cache.get_user(data['user']['id'], local: @auth.user?)
541
+ user ||= MijDiscord::Data::User.new(data['user'], self)
542
+ trigger_event(:ban_user, self, server, user)
543
+
544
+ when :GUILD_BAN_REMOVE
545
+ server = @cache.get_server(data['guild_id'])
546
+ user = @cache.get_user(data['user']['id'], local: @auth.user?)
547
+ user ||= MijDiscord::Data::User.new(data['user'], self)
548
+ trigger_event(:unban_user, self, server, user)
549
+
550
+ when :MESSAGE_CREATE
551
+ channel = @cache.get_channel(data['channel_id'], nil)
552
+ message = channel.cache.put_message(data)
553
+
554
+ return if ignored_user?(data['author']['id'])
555
+ trigger_event(:create_message, self, message)
556
+
557
+ if message.channel.private?
558
+ trigger_event(:private_message, self, message)
559
+ else
560
+ trigger_event(:channel_message, self, message)
561
+ end
562
+
563
+ when :MESSAGE_ACK
564
+ # Do nothing with message acknowledgement
565
+
566
+ when :MESSAGE_UPDATE
567
+ author = data['author']
568
+ return if author.nil?
569
+
570
+ channel = @cache.get_channel(data['channel_id'], nil)
571
+ message = channel.cache.put_message(data, update: true)
572
+
573
+ return if ignored_user?(author['id'])
574
+ trigger_event(:edit_message, self, message)
575
+
576
+ when :MESSAGE_DELETE
577
+ channel = @cache.get_channel(data['channel_id'], nil)
578
+ channel.cache.remove_message(data['id'])
579
+
580
+ trigger_event(:delete_message, self, data)
581
+
582
+ when :MESSAGE_DELETE_BULK
583
+ messages = data['ids'].map {|x| {'id' => x, 'channel_id' => data['channel_id']} }
584
+ messages.each {|x| trigger_event(:delete_message, self, x) }
585
+
586
+ when :MESSAGE_REACTION_ADD
587
+ channel = @cache.get_channel(data['channel_id'], nil)
588
+ message = channel.cache.get_message(data['message_id'], local: true)
589
+ message.update_reaction(add: data) if message
590
+
591
+ return if ignored_user?(data['user_id'])
592
+ trigger_event(:add_reaction, self, data)
593
+ trigger_event(:toggle_reaction, self, data)
594
+
595
+ when :MESSAGE_REACTION_REMOVE
596
+ channel = @cache.get_channel(data['channel_id'], nil)
597
+ message = channel.cache.get_message(data['message_id'], local: true)
598
+ message.update_reaction(remove: data) if message
599
+
600
+ return if ignored_user?(data['user_id'])
601
+ trigger_event(:remove_reaction, self, data)
602
+ trigger_event(:toggle_reaction, self, data)
603
+
604
+ when :MESSAGE_REACTION_REMOVE_ALL
605
+ channel = @cache.get_channel(data['channel_id'], nil)
606
+ message = channel.cache.get_message(data['message_id'], local: true)
607
+ message.update_reaction(clear: true) if message
608
+
609
+ trigger_event(:clear_reactions, self, data)
610
+
611
+ when :TYPING_START
612
+ begin
613
+ return if ignored_user?(data['user_id'])
614
+ trigger_event(:start_typing, self, data)
615
+ rescue MijDiscord::Errors::Forbidden
616
+ # Ignoring the channel we can't access
617
+ # Why is this even sent? :S
618
+ end
619
+
620
+ when :USER_UPDATE
621
+ user = @cache.put_user(data, update: true)
622
+ @profile.update_data(data) if @profile == user
623
+
624
+ trigger_event(:update_user, self, user)
625
+
626
+ when :PRESENCE_UPDATE
627
+ # Bullshit logic due to Discord gateway sending this in a stupid way
628
+ # TODO: Rewrite relevant parts for better handling?
629
+ if data['guild_id']
630
+ server = @cache.get_server(data['guild_id'])
631
+ user = server.cache.get_member(data['user']['id'])
632
+
633
+ user&.update_presence(data)
634
+ user&.update_data(data)
635
+ else
636
+ user = @cache.get_user(data['user']['id'])
637
+
638
+ user&.update_presence(data)
639
+ user&.update_data(data['user'])
640
+
641
+ if @profile == user
642
+ @profile.update_presence(data)
643
+ @profile.update_data(data['user'])
644
+ end
645
+ end
646
+
647
+ trigger_event(:update_presence, self, data)
648
+
649
+ when :VOICE_STATE_UPDATE
650
+ server = @cache.get_server(data['guild_id'])
651
+ state = server.update_voice_state(data)
652
+ trigger_event(:update_voice_state, self, state)
653
+
654
+ else
655
+ MijDiscord::LOGGER.warn('Dispatch') { "Unhandled gateway event type: #{type}" }
656
+ trigger_event(:unhandled, self, type, data)
657
+ end
658
+ rescue => exc
659
+ MijDiscord::LOGGER.error('Dispatch') { 'An error occurred in dispatch handler' }
660
+ MijDiscord::LOGGER.error('Dispatch') { exc }
661
+ end
662
+
663
+ def inspect
664
+ MijDiscord.make_inspect(self, :auth)
665
+ end
666
+
667
+ private
668
+
669
+ def gateway_check
670
+ raise 'A gateway connection is required for this action' unless connected?
671
+ end
672
+
673
+ def notify_ready
674
+ @gateway.notify_ready
675
+
676
+ trigger_event(:ready, self)
677
+
678
+ if @auth.user?
679
+ guilds = @cache.list_servers.map(&:id)
680
+ @gateway.send_request_guild_sync(guilds)
681
+ end
682
+ end
683
+
684
+ def trigger_event(name, *args)
685
+ @event_dispatchers[name]&.trigger(args)
686
+ end
687
+ end
678
688
  end