mij-discord 1.0.10 → 1.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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