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,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MijDiscord::Data
4
+ class User
5
+ include IDObject
6
+
7
+ attr_reader :bot
8
+
9
+ attr_reader :username
10
+ alias_method :name, :username
11
+
12
+ attr_reader :discriminator
13
+ alias_method :tag, :discriminator
14
+
15
+ attr_reader :bot_account
16
+ alias_method :bot_account?, :bot_account
17
+
18
+ attr_reader :avatar_id
19
+
20
+ attr_reader :status
21
+
22
+ attr_reader :game
23
+
24
+ attr_reader :stream_url
25
+
26
+ attr_reader :stream_type
27
+
28
+ def initialize(data, bot)
29
+ @bot = bot
30
+
31
+ @id = data['id'].to_i
32
+ @bot_account = !!data['bot']
33
+ update_data(data)
34
+
35
+ @status = :offline
36
+
37
+ @roles = {}
38
+ end
39
+
40
+ def update_data(data)
41
+ @username = data.fetch('username', @username)
42
+ @discriminator = data.fetch('discriminator', @discriminator)
43
+ @avatar_id = data.fetch('avatar', @avatar_id)
44
+ end
45
+
46
+ def update_presence(presence)
47
+ @status = presence['status'].to_sym
48
+
49
+ if (game = presence['game'])
50
+ @game = game['name']
51
+ @stream_url = game['url']
52
+ @stream_type = game['type']
53
+ else
54
+ @game = @stream_url = @stream_type = nil
55
+ end
56
+ end
57
+
58
+ def mention
59
+ "<@#{@id}>"
60
+ end
61
+
62
+ alias_method :to_s, :mention
63
+
64
+ def distinct
65
+ "#{@username}##{@discriminator}"
66
+ end
67
+
68
+ def pm(text: nil, embed: nil)
69
+ if text || embed
70
+ pm.send_message(text: text || '', embed: embed)
71
+ else
72
+ @bot.pm_channel(@id)
73
+ end
74
+ end
75
+
76
+ alias_method :dm, :pm
77
+
78
+ def send_file(file, caption: nil)
79
+ pm.send_file(file, caption: caption)
80
+ end
81
+
82
+ def on(server)
83
+ id = server.to_id
84
+ @bot.server(id).member(@id)
85
+ end
86
+
87
+ def webhook?
88
+ @discriminator == '0000'
89
+ end
90
+
91
+ def current_bot?
92
+ @bot.profile == self
93
+ end
94
+
95
+ def online?
96
+ @status == :online?
97
+ end
98
+
99
+ def idle?
100
+ @status == :idle
101
+ end
102
+
103
+ alias_method :away?, :idle?
104
+
105
+ def dnd?
106
+ @status == :dnd
107
+ end
108
+
109
+ alias_method :busy?, :dnd?
110
+
111
+ def invisible?
112
+ @status == :invisible
113
+ end
114
+
115
+ alias_method :hidden?, :invisible?
116
+
117
+ def offline?
118
+ @status == :offline
119
+ end
120
+
121
+ def member?
122
+ false
123
+ end
124
+
125
+ def avatar_url(format = nil)
126
+ return MijDiscord::Core::API::User.default_avatar(@discriminator) unless @avatar_id
127
+ MijDiscord::Core::API::User.avatar_url(@id, @avatar_id, format)
128
+ end
129
+
130
+ def inspect
131
+ %(<User id=#{@id} name="#{@username}" tag=#{@discriminator}>)
132
+ end
133
+ end
134
+
135
+ class Profile < User
136
+ attr_reader :mfa_enabled
137
+ alias_method :mfa_enabled?, :mfa_enabled
138
+
139
+ def update_data(data)
140
+ super(data)
141
+
142
+ @mfa_enabled = !!data['mfa_enabled']
143
+ end
144
+
145
+ def set_username(name)
146
+ response = MijDiscord::Core::API::User.update_profile(@bot.token, name, nil)
147
+ update_data(JSON.parse(response))
148
+ nil
149
+ end
150
+
151
+ alias_method :username=, :set_username
152
+ alias_method :set_name, :set_username
153
+ alias_method :name=, :set_username
154
+
155
+ def set_avatar(data, format = :png)
156
+ if data.is_a?(String)
157
+ data = "data:image/#{format};base64,#{data}"
158
+ elsif data.respond_to?(:read)
159
+ data.binmode if data.respond_to?(:binmode)
160
+ data = Base64.strict_encode64(data.read)
161
+ data = "data:image/#{format};base64,#{data}"
162
+ else
163
+ raise ArgumentError, 'Invalid avatar data provided'
164
+ end
165
+
166
+ response = MijDiscord::Core::API::User.update_profile(@bot.token, @username, data)
167
+ update_data(JSON.parse(response))
168
+ nil
169
+ end
170
+
171
+ alias_method :avatar=, :set_avatar
172
+ end
173
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MijDiscord::Data
4
+ class VoiceState
5
+ attr_reader :user
6
+
7
+ attr_reader :mute
8
+ alias_method :mute?, :mute
9
+
10
+ attr_reader :deaf
11
+ alias_method :deaf?, :deaf
12
+
13
+ attr_reader :self_mute
14
+ alias_method :self_mute?, :self_mute
15
+
16
+ attr_reader :self_deaf
17
+ alias_method :self_deaf?, :self_deaf
18
+
19
+ attr_reader :voice_channel
20
+ alias_method :channel, :voice_channel
21
+
22
+ def initialize(user)
23
+ @user = user
24
+ end
25
+
26
+ def update_data(channel, data)
27
+ @voice_channel = channel
28
+
29
+ @mute = data.fetch('mute', @mute)
30
+ @deaf = data.fetch('deaf', @deaf)
31
+ @self_mute = data.fetch('self_mute', @self_mute)
32
+ @self_deaf = data.fetch('self_deaf', @self_deaf)
33
+ end
34
+ end
35
+
36
+ class VoiceRegion
37
+ attr_reader :id
38
+ alias_method :to_s, :id
39
+
40
+ attr_reader :name
41
+
42
+ attr_reader :sample_hostname
43
+
44
+ attr_reader :sample_port
45
+
46
+ attr_reader :vip
47
+
48
+ attr_reader :optimal
49
+
50
+ attr_reader :deprecated
51
+
52
+ attr_reader :custom
53
+
54
+ def initialize(data)
55
+ @id = data['id']
56
+
57
+ @name = data['name']
58
+
59
+ @sample_hostname = data['sample_hostname']
60
+ @sample_port = data['sample_port']
61
+
62
+ @vip = data['vip']
63
+ @optimal = data['optimal']
64
+ @deprecated = data['deprecated']
65
+ @custom = data['custom']
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MijDiscord::Events
4
+ class EventBase
5
+ FilterMatch = Struct.new(:field, :on, :cmp)
6
+
7
+ def initialize(*args)
8
+ # Nothing
9
+ end
10
+
11
+ def trigger?(params)
12
+ filters = self.class.event_filters
13
+
14
+ result = params.map do |key, param|
15
+ next true unless filters.has_key?(key)
16
+
17
+ check = filters[key].map do |match|
18
+ on, field, cmp = match.on, match.field, match.cmp
19
+
20
+ is_match = case on
21
+ when Array
22
+ on.reduce(false) {|a,x| a || trigger_match?(x, param) }
23
+ else
24
+ trigger_match?(on, param)
25
+ end
26
+
27
+ next false unless is_match
28
+
29
+ value = case field
30
+ when Array
31
+ field.reduce(self) {|a,x| a.respond_to?(x) ? a.send(x) : nil }
32
+ else
33
+ respond_to?(field) ? send(field) : nil
34
+ end
35
+
36
+ case cmp
37
+ when :eql?
38
+ value == param
39
+ when :neq?
40
+ value != param
41
+ when :case
42
+ param === value
43
+ when Proc
44
+ cmp.call(value, param)
45
+ else
46
+ false
47
+ end
48
+ end
49
+
50
+ check.reduce(false, &:|)
51
+ end
52
+
53
+ result.reduce(true, &:&)
54
+ end
55
+
56
+ private
57
+
58
+ def trigger_match?(match, key)
59
+ case match
60
+ when :any, :all
61
+ true
62
+ when :id_obj
63
+ key.respond_to?(:to_id)
64
+ when Class
65
+ match === key
66
+ end
67
+ end
68
+
69
+ class << self
70
+ attr_reader :event_filters
71
+
72
+ def filter_match(key, field: key, on: :any, cmp: nil, &block)
73
+ raise ArgumentError, 'No comparison function provided' unless cmp || block
74
+
75
+ # @event_filters ||= superclass&.event_filters&.dup || {}
76
+ filter = (@event_filters[key] ||= [])
77
+ filter << FilterMatch.new(field, on, block || cmp)
78
+ end
79
+
80
+ def delegate_method(*names, to:)
81
+ names.each do |name|
82
+ define_method(name) do |*arg|
83
+ send(to).send(name, *arg)
84
+ end
85
+ end
86
+ end
87
+
88
+ def inherited(sc)
89
+ filters = @event_filters&.dup || {}
90
+ sc.instance_variable_set(:@event_filters, filters)
91
+ end
92
+ end
93
+ end
94
+
95
+ class DispatcherBase
96
+ Callback = Struct.new(:key, :block, :filter)
97
+
98
+ def initialize(klass)
99
+ raise ArgumentError, 'Class must inherit from EventBase' unless klass < EventBase
100
+
101
+ @klass, @callbacks = klass, {}
102
+ end
103
+
104
+ def add_callback(key = nil, **filter, &block)
105
+ raise ArgumentError, 'No callback block provided' if block.nil?
106
+
107
+ key = block.object_id if key.nil?
108
+ @callbacks[key] = Callback.new(key, block, filter)
109
+ key
110
+ end
111
+
112
+ def remove_callback(key)
113
+ @callbacks.delete(key)
114
+ nil
115
+ end
116
+
117
+ def callbacks
118
+ @callbacks.values
119
+ end
120
+
121
+ def trigger(event_args, block_args = nil)
122
+ event = @klass.new(*event_args)
123
+
124
+ @callbacks.each do |_, cb|
125
+ execute_callback(cb, event, block_args) if event.trigger?(cb.filter)
126
+ end
127
+ end
128
+
129
+ alias_method :raise, :trigger
130
+
131
+ # Must implement execute_callback
132
+ end
133
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MijDiscord::Events
4
+ class Generic < EventBase
5
+ attr_reader :bot
6
+
7
+ def initialize(bot)
8
+ @bot = bot
9
+ end
10
+ end
11
+
12
+ class Ready < Generic; end
13
+
14
+ class Heartbeat < Generic; end
15
+
16
+ class Connect < Generic; end
17
+
18
+ class Disconnect < Generic; end
19
+
20
+ class Exception < Generic
21
+ attr_reader :type
22
+
23
+ attr_reader :payload
24
+
25
+ attr_reader :exception
26
+
27
+ filter_match(:type, on: Symbol, cmp: :eql?)
28
+
29
+ def initialize(bot, type, exception, payload = nil)
30
+ super(bot)
31
+
32
+ @type, @exception, @payload = type, exception, payload
33
+ end
34
+ end
35
+
36
+ class UpdateUser < Generic
37
+ attr_reader :user
38
+
39
+ filter_match(:user, field: [:user, :name], on: [String, Regexp], cmp: :case)
40
+ filter_match(:user, on: :id_obj, cmp: :eql?)
41
+
42
+ def initialize(bot, user)
43
+ super(bot)
44
+
45
+ @user = user
46
+ end
47
+ end
48
+
49
+ class EventDispatcher < MijDiscord::Events::DispatcherBase
50
+ attr_reader :threads
51
+
52
+ def initialize(klass, bot)
53
+ super(klass)
54
+
55
+ @bot, @threads = bot, []
56
+ end
57
+
58
+ def execute_callback(callback, event, _)
59
+ Thread.new do
60
+ thread = Thread.current
61
+
62
+ @threads << thread
63
+ thread[:mij_discord] = "event-#{callback.key}"
64
+
65
+ begin
66
+ callback.block.call(event, callback.key)
67
+ rescue LocalJumpError
68
+ # Allow premature return from callback block
69
+ rescue => exc
70
+ @bot.handle_exception(:event, exc, event)
71
+
72
+ MijDiscord::LOGGER.error('Events') { 'An error occurred in event callback' }
73
+ MijDiscord::LOGGER.error('Events') { exc }
74
+ ensure
75
+ @threads.delete(thread)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end