mij-discord 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +6 -0
  5. data/LICENSE +21 -0
  6. data/README.md +35 -0
  7. data/Rakefile +10 -0
  8. data/bin/console +7 -0
  9. data/bin/setup +6 -0
  10. data/lib/mij-discord.rb +56 -0
  11. data/lib/mij-discord/bot.rb +579 -0
  12. data/lib/mij-discord/cache.rb +298 -0
  13. data/lib/mij-discord/core/api.rb +228 -0
  14. data/lib/mij-discord/core/api/channel.rb +416 -0
  15. data/lib/mij-discord/core/api/invite.rb +43 -0
  16. data/lib/mij-discord/core/api/server.rb +465 -0
  17. data/lib/mij-discord/core/api/user.rb +144 -0
  18. data/lib/mij-discord/core/errors.rb +106 -0
  19. data/lib/mij-discord/core/gateway.rb +505 -0
  20. data/lib/mij-discord/data.rb +65 -0
  21. data/lib/mij-discord/data/application.rb +38 -0
  22. data/lib/mij-discord/data/channel.rb +404 -0
  23. data/lib/mij-discord/data/embed.rb +115 -0
  24. data/lib/mij-discord/data/emoji.rb +62 -0
  25. data/lib/mij-discord/data/invite.rb +87 -0
  26. data/lib/mij-discord/data/member.rb +174 -0
  27. data/lib/mij-discord/data/message.rb +206 -0
  28. data/lib/mij-discord/data/permissions.rb +121 -0
  29. data/lib/mij-discord/data/role.rb +99 -0
  30. data/lib/mij-discord/data/server.rb +359 -0
  31. data/lib/mij-discord/data/user.rb +173 -0
  32. data/lib/mij-discord/data/voice.rb +68 -0
  33. data/lib/mij-discord/events.rb +133 -0
  34. data/lib/mij-discord/events/basic.rb +80 -0
  35. data/lib/mij-discord/events/channel.rb +50 -0
  36. data/lib/mij-discord/events/member.rb +66 -0
  37. data/lib/mij-discord/events/message.rb +150 -0
  38. data/lib/mij-discord/events/server.rb +102 -0
  39. data/lib/mij-discord/logger.rb +20 -0
  40. data/lib/mij-discord/version.rb +5 -0
  41. data/mij-discord.gemspec +31 -0
  42. metadata +154 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f1bb367b3fa105f6f685bf536ffc5ed364c2bf1a
4
+ data.tar.gz: 69d324ed5ec538deec69e778e725053240bc6faf
5
+ SHA512:
6
+ metadata.gz: cfae532e6dbad1759046637cc4049a8b2d000ac40c5d4ffc376455c5c000536d78f6d4168d288f8e80eaadfef01dbaacec391debbdd7cbeb4988f4751c3f7c75
7
+ data.tar.gz: 9bc6a5388a4f3d5961c1682b011043e3940fc5cc6d4a9a12d6e9f37c51cda80ea2589de5b67754bf642904c77bbf505099d1a7b9b34941c2c0dd23e2c417ae7b
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /.idea/
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.1
5
+ before_install: gem install bundler -v 1.15.3
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in MijDiscord.gemspec
6
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017-2018 Mijyuoon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,35 @@
1
+ # MijDiscord
2
+
3
+ My Discord bot library that's partially based on Discordrb. Was made because I found Discordrb to be not satisfactory for my purposes.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'mij-discord'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+
21
+ ```
22
+ $ gem install mij-discord
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ TODO: Write usage instructions here
28
+
29
+ ## Contributing
30
+
31
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Mijyuoon/MijDiscord.
32
+
33
+ ## License
34
+
35
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << 'test'
6
+ t.libs << 'lib'
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'mij-discord'
5
+
6
+ require 'irb'
7
+ IRB.start(__FILE__)
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+ require 'logger'
5
+ require 'base64'
6
+ require 'thread'
7
+ require 'json'
8
+ require 'time'
9
+ require 'socket'
10
+ require 'openssl'
11
+ require 'zlib'
12
+ require 'websocket-client-simple'
13
+ require 'rest-client'
14
+
15
+ require 'mij-discord/version'
16
+ require 'mij-discord/logger'
17
+ require 'mij-discord/cache'
18
+ require 'mij-discord/events'
19
+
20
+ require 'mij-discord/core/gateway'
21
+ require 'mij-discord/core/errors'
22
+ require 'mij-discord/core/api'
23
+ require 'mij-discord/core/api/channel'
24
+ require 'mij-discord/core/api/invite'
25
+ require 'mij-discord/core/api/server'
26
+ require 'mij-discord/core/api/user'
27
+
28
+ require 'mij-discord/data'
29
+ require 'mij-discord/data/invite'
30
+ require 'mij-discord/data/voice'
31
+ require 'mij-discord/data/permissions'
32
+ require 'mij-discord/data/application'
33
+ require 'mij-discord/data/user'
34
+ require 'mij-discord/data/member'
35
+ require 'mij-discord/data/role'
36
+ require 'mij-discord/data/channel'
37
+ require 'mij-discord/data/server'
38
+ require 'mij-discord/data/embed'
39
+ require 'mij-discord/data/emoji'
40
+ require 'mij-discord/data/message'
41
+
42
+ require 'mij-discord/events/basic'
43
+ require 'mij-discord/events/server'
44
+ require 'mij-discord/events/member'
45
+ require 'mij-discord/events/channel'
46
+ require 'mij-discord/events/message'
47
+
48
+ require 'mij-discord/bot'
49
+
50
+ class Integer
51
+ alias_method :to_id, :itself
52
+ end
53
+
54
+ class String
55
+ alias_method :to_id, :to_i
56
+ end
@@ -0,0 +1,579 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MijDiscord
4
+ class Bot
5
+ EVENTS = {
6
+ ready: MijDiscord::Events::Ready,
7
+ heartbeat: MijDiscord::Events::Heartbeat,
8
+ connect: MijDiscord::Events::Connect,
9
+ disconnect: MijDiscord::Events::Disconnect,
10
+ exception: MijDiscord::Events::Exception,
11
+
12
+ update_user: MijDiscord::Events::UpdateUser,
13
+ create_server: MijDiscord::Events::CreateServer,
14
+ update_server: MijDiscord::Events::UpdateServer,
15
+ delete_server: MijDiscord::Events::DeleteServer,
16
+ update_emoji: MijDiscord::Events::UpdateEmoji,
17
+ ban_user: MijDiscord::Events::BanUser,
18
+ unban_user: MijDiscord::Events::UnbanUser,
19
+
20
+ create_role: MijDiscord::Events::CreateRole,
21
+ update_role: MijDiscord::Events::UpdateRole,
22
+ delete_role: MijDiscord::Events::DeleteRole,
23
+ create_member: MijDiscord::Events::CreateMember,
24
+ update_member: MijDiscord::Events::UpdateMember,
25
+ delete_member: MijDiscord::Events::DeleteMember,
26
+
27
+ create_channel: MijDiscord::Events::CreateChannel,
28
+ update_channel: MijDiscord::Events::UpdateChannel,
29
+ delete_channel: MijDiscord::Events::DeleteChannel,
30
+ add_recipient: MijDiscord::Events::AddRecipient,
31
+ remove_recipient: MijDiscord::Events::RemoveRecipient,
32
+
33
+ create_message: MijDiscord::Events::CreateMessage,
34
+ channel_message: MijDiscord::Events::ChannelMessage,
35
+ private_message: MijDiscord::Events::PrivateMessage,
36
+ edit_message: MijDiscord::Events::EditMessage,
37
+ delete_message: MijDiscord::Events::DeleteMessage,
38
+ add_reaction: MijDiscord::Events::AddReaction,
39
+ remove_reaction: MijDiscord::Events::RemoveReaction,
40
+ toggle_reaction: MijDiscord::Events::ToggleReaction,
41
+ clear_reactions: MijDiscord::Events::ClearReactions,
42
+ start_typing: MijDiscord::Events::StartTyping,
43
+
44
+ update_presence: MijDiscord::Events::UpdatePresence,
45
+ update_playing: MijDiscord::Events::UpdatePlaying,
46
+ update_voice_state: MijDiscord::Events::UpdateVoiceState,
47
+ }.freeze
48
+
49
+ UNAVAILABLE_SERVER_TIMEOUT = 10
50
+
51
+ USER_STATUS = {
52
+ online: :online,
53
+ idle: :idle,
54
+ away: :idle,
55
+ dnd: :dnd,
56
+ busy: :dnd,
57
+ invisible: :invisible,
58
+ hidden: :invisible,
59
+ offline: :offline
60
+ }.freeze
61
+
62
+ attr_reader :name
63
+
64
+ attr_reader :type
65
+
66
+ attr_reader :client_id
67
+
68
+ attr_reader :token
69
+
70
+ attr_reader :shard_key
71
+
72
+ attr_reader :profile
73
+
74
+ attr_reader :gateway
75
+
76
+ attr_reader :cache
77
+
78
+ def initialize(client_id:, token:, type: :bot, name: nil,
79
+ shard_id: nil, num_shards: nil, ignore_bots: false, ignore_self: true)
80
+ @client_id, @type, @name = client_id.to_id, type, name || ''
81
+
82
+ @token = case @type
83
+ when :bot then "Bot #{token}"
84
+ when :user then "#{token}"
85
+ else raise ArgumentError, 'Invalid bot type'
86
+ end
87
+
88
+ @cache = MijDiscord::Cache::BotCache.new(self)
89
+
90
+ @shard_key = [shard_id, num_shards] if num_shards
91
+ @gateway = MijDiscord::Core::Gateway.new(self, @token, @shard_key)
92
+
93
+ @ignore_bots, @ignore_self, @ignored_ids = ignore_bots, ignore_self, Set.new
94
+
95
+ @unavailable_servers = 0
96
+
97
+ @event_dispatchers = {}
98
+ end
99
+
100
+ def connect(async = true)
101
+ @gateway.run_async
102
+ @gateway.sync unless async
103
+ nil
104
+ end
105
+
106
+ def sync
107
+ @gateway.sync
108
+ nil
109
+ end
110
+
111
+ def disconnect(no_sync = false)
112
+ @gateway.stop(no_sync)
113
+ nil
114
+ end
115
+
116
+ alias_method :shutdown, :disconnect
117
+
118
+ def connected?
119
+ @gateway.open?
120
+ end
121
+
122
+ def servers
123
+ gateway_check
124
+ @cache.list_servers
125
+ end
126
+
127
+ def server(id)
128
+ gateway_check
129
+ @cache.get_server(id)
130
+ end
131
+
132
+ def channels
133
+ gateway_check
134
+ @cache.list_channels
135
+ end
136
+
137
+ def channel(id, server = nil)
138
+ gateway_check
139
+ @cache.get_channel(id, server)
140
+ end
141
+
142
+ def pm_channel(id)
143
+ gateway_check
144
+ @cache.get_pm_channel(id)
145
+ end
146
+
147
+ alias_method :dm_channel, :pm_channel
148
+
149
+ def users
150
+ gateway_check
151
+ @cache.list_users
152
+ end
153
+
154
+ def user(id)
155
+ gateway_check
156
+ @cache.get_user(id)
157
+ end
158
+
159
+ def members(server_id)
160
+ gateway_check
161
+ server(server_id)&.members
162
+ end
163
+
164
+ def member(server_id, id)
165
+ gateway_check
166
+ server(server_id)&.member(id)
167
+ end
168
+
169
+ def roles(server_id)
170
+ gateway_check
171
+ server(server_id)&.roles
172
+ end
173
+
174
+ def role(server_id, id)
175
+ gateway_check
176
+ server(server_id)&.role(id)
177
+ end
178
+
179
+ def emojis(server_id)
180
+ gateway_check
181
+ server(server_id)&.emojis
182
+ end
183
+
184
+ def emoji(server_id, id)
185
+ gateway_check
186
+ server(server_id)&.emoji(id)
187
+ end
188
+
189
+ def application
190
+ raise RuntimeError, 'Cannot get OAuth application for non-bot user' if @type != :bot
191
+
192
+ response = MijDiscord::Core::API.oauth_application(@token)
193
+ MijDiscord::Data::Application.new(JSON.parse(response), self)
194
+ end
195
+
196
+ def invite(invite)
197
+ code = parse_invite_code(invite)
198
+ response = MijDiscord::Core::API::Invite.resolve(token, code)
199
+ MijDiscord::Data::Invite.new(JSON.parse(response), self)
200
+ end
201
+
202
+ def accept_invite(invite)
203
+ code = parse_invite_code(invite)
204
+ API::Invite.accept(token, code)
205
+ nil
206
+ end
207
+
208
+ def make_invite_url(server: nil, permissions: nil)
209
+ url = "https://discordapp.com/oauth2/authorize?scope=bot&client_id=#{@client_id}".dup
210
+ url << "&permissions=#{permissions.to_i}" if permissions.respond_to?(:to_i)
211
+ url << "&guild_id=#{server.to_id}" if server.respond_to?(:to_id)
212
+ url
213
+ end
214
+
215
+ def create_server(name, region = 'eu-central')
216
+ response = API::Server.create(@token, name, region)
217
+ id = JSON.parse(response)['id'].to_i
218
+
219
+ loop do
220
+ server = @cache.get_server(id, local: true)
221
+ return server if server
222
+
223
+ sleep(0.1)
224
+ end
225
+ end
226
+
227
+ def parse_invite_code(invite)
228
+ case invite
229
+ when %r[(\w+)] then $1
230
+ when %r[(?:https?://)?discord\.gg/(\w+)]i then $1
231
+ when %r[https?://discordapp\.com/invite/(\w+)]i then $1
232
+ when MijDiscord::Data::Invite then invite.code
233
+ else raise ArgumentError, 'Invalid invite format'
234
+ end
235
+ end
236
+
237
+ def parse_mention(mention, server_id = nil)
238
+ gateway_check
239
+
240
+ case mention
241
+ when /<@!?(\d+)>/
242
+ server_id ? member(server_id, $1) : user($1)
243
+ when /<@&(\d+)>/
244
+ role = role(server_id, $1)
245
+ return role if role
246
+
247
+ servers.each do |sv|
248
+ role = sv.role($1)
249
+ return role if role
250
+ end
251
+ when /<:\w+:(\d+)>/
252
+ emoji = emoji(server_id, $1)
253
+ return emoji if emoji
254
+
255
+ servers.each do |sv|
256
+ emoji = sv.emoji($1)
257
+ return emoji if emoji
258
+ end
259
+ end
260
+ end
261
+
262
+ def add_event(type, key = nil, **filter, &block)
263
+ raise ArgumentError, "Invalid event type: #{type}" unless EVENTS[type]
264
+
265
+ event = (@event_dispatchers[type] ||= MijDiscord::Events::EventDispatcher.new(EVENTS[type], self))
266
+ event.add_callback(key, filter, &block)
267
+ end
268
+
269
+ def remove_event(type, key)
270
+ raise ArgumentError, "Invalid event type: #{type}" unless EVENTS[type]
271
+
272
+ @event_dispatchers[type]&.remove_callback(key)
273
+ nil
274
+ end
275
+
276
+ def events(type)
277
+ raise ArgumentError, "Invalid event type: #{type}" unless EVENTS[type]
278
+
279
+ @event_dispatchers[type]&.callbacks || []
280
+ end
281
+
282
+ def ignore_user(user)
283
+ @ignored_ids << user.to_id
284
+ nil
285
+ end
286
+
287
+ def unignore_user(user)
288
+ @ignored_ids.delete(user.to_id)
289
+ nil
290
+ end
291
+
292
+ def ignored_user?(user)
293
+ @ignore_self && user.to_id == @client_id || @ignored_ids.include?(user.to_id)
294
+ end
295
+
296
+ def change_status(status: nil, game: nil, url: nil)
297
+ gateway_check
298
+
299
+ status = status.nil? ? @profile.status : USER_STATUS[status]
300
+ raise ArgumentError, "Status '#{status}' is not valid" unless status
301
+
302
+ game_obj = case game
303
+ when false
304
+ nil
305
+ when nil
306
+ {'name' => @profile.game, 'url' => @profile.stream_url, 'type' => @profile.stream_type}
307
+ else
308
+ {'name' => game, 'url' => url, 'type' => url ? 1 : 0}
309
+ end
310
+
311
+ game_obj&.reject! {|_,v| v.nil? }
312
+ game_obj = nil if game_obj&.empty?
313
+
314
+ @gateway.send_status_update(status, nil, game_obj, false)
315
+ @profile.update_presence('status' => status, 'game' => game_obj)
316
+ nil
317
+ end
318
+
319
+ def handle_heartbeat
320
+ trigger_event(:heartbeat, self)
321
+ end
322
+
323
+ def handle_exception(type, exception, payload = nil)
324
+ return if type == :event && payload&.is_a?(MijDiscord::Events::Exception)
325
+
326
+ trigger_event(:exception, self, type, exception, payload)
327
+ end
328
+
329
+ def handle_dispatch(type, data)
330
+ MijDiscord::LOGGER.debug('Dispatch') { "<#{type} #{data.inspect}>" }
331
+
332
+ if @unavailable_servers > 0 && Time.now > @unavailable_servers_timeout
333
+ MijDiscord::LOGGER.warn('Dispatch') { "Proceeding with #{@unavailable_servers} servers still unavailable" }
334
+
335
+ @unavailable_servers = 0
336
+ notify_ready
337
+ end
338
+
339
+ case type
340
+ when :CONNECT
341
+ trigger_event(:connect, self)
342
+
343
+ when :DISCONNECT
344
+ trigger_event(:disconnect, self)
345
+
346
+ when :READY
347
+ @cache.reset
348
+
349
+ @profile = MijDiscord::Data::Profile.new(data['user'], self)
350
+ @profile.update_presence('status' => :online)
351
+
352
+ @unavailable_servers = 0
353
+ @unavailable_servers_timeout = Time.now + UNAVAILABLE_SERVER_TIMEOUT
354
+
355
+ data['guilds'].each do |sv|
356
+ if sv['unavailable'].eql?(true)
357
+ @unavailable_servers += 1
358
+ else
359
+ @cache.put_server(sv)
360
+ end
361
+ end
362
+
363
+ data['private_channels'].each do |ch|
364
+ @cache.put_channel(ch, nil)
365
+ end
366
+
367
+ notify_ready if @unavailable_servers.zero?
368
+
369
+ when :GUILD_MEMBERS_CHUNK
370
+ server = @cache.get_server(data['guild_id'])
371
+ server.update_members_chunk(data['members'])
372
+
373
+ when :GUILD_CREATE
374
+ server = @cache.put_server(data)
375
+
376
+ if data['unavailable'].eql?(false)
377
+ @unavailable_servers -= 1
378
+ @unavailable_servers_timeout = Time.now + UNAVAILABLE_SERVER_TIMEOUT
379
+
380
+ notify_ready if @unavailable_servers.zero?
381
+ return
382
+ end
383
+
384
+ trigger_event(:create_server, self, server)
385
+
386
+ when :GUILD_UPDATE
387
+ server = @cache.put_server(data, update: true)
388
+ trigger_event(:update_server, self, server)
389
+
390
+ when :GUILD_DELETE
391
+ server = @cache.remove_server(data['id'])
392
+
393
+ if data['unavailable'].eql?(true)
394
+ MijDiscord::LOGGER.warn('Dispatch') { "Server <#{server.id}> died due to outage" }
395
+ return
396
+ end
397
+
398
+ trigger_event(:delete_server, self, server)
399
+
400
+ when :CHANNEL_CREATE
401
+ channel = @cache.put_channel(data, nil)
402
+ trigger_event(:create_channel, self, channel)
403
+
404
+ when :CHANNEL_UPDATE
405
+ channel = @cache.put_channel(data, nil, update: true)
406
+ trigger_event(:update_channel, self, channel)
407
+
408
+ when :CHANNEL_DELETE
409
+ channel = @cache.remove_channel(data['id'])
410
+ trigger_event(:delete_channel, self, channel)
411
+
412
+ when :CHANNEL_RECIPIENT_ADD
413
+ channel = @cache.get_channel(data['channel_id'], nil)
414
+ recipient = channel.update_recipient(add: data['user'])
415
+ trigger_event(:add_recipient, self, channel, recipient)
416
+
417
+ when :CHANNEL_RECIPIENT_REMOVE
418
+ channel = @cache.get_channel(data['channel_id'], nil)
419
+ recipient = channel.update_recipient(remove: data['user'])
420
+ trigger_event(:remove_recipient, self, channel, recipient)
421
+
422
+ when :GUILD_MEMBER_ADD
423
+ server = @cache.get_server(data['guild_id'])
424
+ member = server.cache.put_member(data)
425
+ trigger_event(:create_member, self, member, server)
426
+
427
+ when :GUILD_MEMBER_UPDATE
428
+ server = @cache.get_server(data['guild_id'])
429
+ member = server.cache.put_member(data, update: true)
430
+ trigger_event(:update_member, self, member, server)
431
+
432
+ when :GUILD_MEMBER_REMOVE
433
+ server = @cache.get_server(data['guild_id'])
434
+ member = server.cache.remove_member(data['user']['id'])
435
+ trigger_event(:delete_member, self, member, server)
436
+
437
+ when :GUILD_ROLE_CREATE
438
+ server = @cache.get_server(data['guild_id'])
439
+ role = server.cache.put_role(data['role'])
440
+ trigger_event(:create_role, self, server, role)
441
+
442
+ when :GUILD_ROLE_UPDATE
443
+ server = @cache.get_server(data['guild_id'])
444
+ role = server.cache.put_role(data['role'], update: true)
445
+ trigger_event(:update_role, self, server, role)
446
+
447
+ when :GUILD_ROLE_DELETE
448
+ server = @cache.get_server(data['guild_id'])
449
+ role = server.cache.remove_role(data['role']['id'])
450
+ trigger_event(:delete_role, self, server, role)
451
+
452
+ when :GUILD_EMOJIS_UPDATE
453
+ server = @cache.get_server(data['guild_id'])
454
+ old_emojis = server.emojis
455
+ server.update_emojis(data)
456
+ trigger_event(:update_emoji, self, server, old_emojis, server.emojis)
457
+
458
+ when :GUILD_BAN_ADD
459
+ server = @cache.get_server(data['guild_id'])
460
+ user = @cache.get_user(data['user']['id'])
461
+ trigger_event(:ban_user, self, server, user)
462
+
463
+ when :GUILD_BAN_REMOVE
464
+ server = @cache.get_server(data['guild_id'])
465
+ user = @cache.get_user(data['user']['id'])
466
+ trigger_event(:unban_user, self, server, user)
467
+
468
+ when :MESSAGE_CREATE
469
+ return if ignored_user?(data['author']['id'])
470
+ return if @ignore_bots && data['author']['bot']
471
+
472
+ channel = @cache.get_channel(data['channel_id'], nil)
473
+ message = channel.cache.put_message(data)
474
+ trigger_event(:create_message, self, message)
475
+
476
+ if message.channel.private?
477
+ trigger_event(:private_message, self, message)
478
+ else
479
+ trigger_event(:channel_message, self, message)
480
+ end
481
+
482
+ when :MESSAGE_ACK
483
+ # Do nothing with message acknowledgement
484
+
485
+ when :MESSAGE_UPDATE
486
+ author = data['author']
487
+ return if author.nil?
488
+
489
+ return if ignored_user?(author['id'])
490
+ return if @ignore_bots && author['bot']
491
+
492
+ channel = @cache.get_channel(data['channel_id'], nil)
493
+ message = channel.cache.put_message(data, update: true)
494
+ trigger_event(:edit_message, self, message)
495
+
496
+ when :MESSAGE_DELETE
497
+ channel = @cache.get_channel(data['channel_id'], nil)
498
+ channel.cache.remove_message(data['id'])
499
+
500
+ trigger_event(:delete_message, self, data)
501
+
502
+ when :MESSAGE_DELETE_BULK
503
+ messages = data['ids'].map {|x| {'id' => x, 'channel_id' => data['channel_id']} }
504
+ messages.each {|x| trigger_event(:delete_message, self, x) }
505
+
506
+ when :MESSAGE_REACTION_ADD
507
+ # Should add full use ignore support?
508
+ return if ignored_user?(data['user_id'])
509
+
510
+ trigger_event(:add_reaction, self, data)
511
+ trigger_event(:toggle_reaction, self, data)
512
+
513
+ when :MESSAGE_REACTION_REMOVE
514
+ # Should add full use ignore support?
515
+ return if ignored_user?(data['user_id'])
516
+
517
+ trigger_event(:remove_reaction, self, data)
518
+ trigger_event(:toggle_reaction, self, data)
519
+
520
+ when :MESSAGE_REACTION_REMOVE_ALL
521
+ trigger_event(:clear_reactions, self, data)
522
+
523
+ when :TYPING_START
524
+ begin
525
+ trigger_event(:start_typing, self, data)
526
+ rescue MijDiscord::Core::Errors::NoPermission
527
+ # Ignoring the channel we can't access
528
+ # Why is this even sent? :S
529
+ end
530
+
531
+ when :USER_UPDATE
532
+ user = @cache.put_user(data, update: true)
533
+ trigger_event(:update_user, self, user)
534
+
535
+ when :PRESENCE_UPDATE
536
+ return unless data['guild_id']
537
+
538
+ server = @cache.get_server(data['guild_id'])
539
+ member = server.cache.put_member(data, update: true)
540
+
541
+ old_game = member.game
542
+ member.update_presence(data)
543
+
544
+ if old_game != member.game
545
+ trigger_event(:update_playing, self, data)
546
+ else
547
+ trigger_event(:update_presence, self, data)
548
+ end
549
+
550
+ when :VOICE_STATE_UPDATE
551
+ server = @cache.get_server(data['guild_id'])
552
+ state = server.update_voice_state(data)
553
+ trigger_event(:update_voice_state, self, state)
554
+
555
+ else
556
+ MijDiscord::LOGGER.warn('Dispatch') { "Unhandled gateway event type: #{type}" }
557
+ end
558
+ rescue => exc
559
+ MijDiscord::LOGGER.error('Dispatch') { 'An error occurred in dispatch handler' }
560
+ MijDiscord::LOGGER.error('Dispatch') { exc }
561
+ end
562
+
563
+ private
564
+
565
+ def gateway_check
566
+ raise RuntimeError, 'A gateway connection is required for this action' unless connected?
567
+ end
568
+
569
+ def notify_ready
570
+ @gateway.notify_ready
571
+
572
+ trigger_event(:ready, self)
573
+ end
574
+
575
+ def trigger_event(name, *args)
576
+ @event_dispatchers[name]&.trigger(args)
577
+ end
578
+ end
579
+ end