mij-discord 1.0.0

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.
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