discord_rda 0.1.3
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +398 -0
- data/lib/discord_rda/bot.rb +842 -0
- data/lib/discord_rda/cache/configurable_cache.rb +283 -0
- data/lib/discord_rda/cache/entity_cache.rb +184 -0
- data/lib/discord_rda/cache/memory_store.rb +143 -0
- data/lib/discord_rda/cache/redis_store.rb +136 -0
- data/lib/discord_rda/cache/store.rb +56 -0
- data/lib/discord_rda/connection/gateway_client.rb +383 -0
- data/lib/discord_rda/connection/invalid_bucket.rb +205 -0
- data/lib/discord_rda/connection/rate_limiter.rb +280 -0
- data/lib/discord_rda/connection/request_queue.rb +340 -0
- data/lib/discord_rda/connection/reshard_manager.rb +328 -0
- data/lib/discord_rda/connection/rest_client.rb +316 -0
- data/lib/discord_rda/connection/rest_proxy.rb +165 -0
- data/lib/discord_rda/connection/scalable_rest_client.rb +526 -0
- data/lib/discord_rda/connection/shard_manager.rb +223 -0
- data/lib/discord_rda/core/async_runtime.rb +108 -0
- data/lib/discord_rda/core/configuration.rb +194 -0
- data/lib/discord_rda/core/logger.rb +188 -0
- data/lib/discord_rda/core/snowflake.rb +121 -0
- data/lib/discord_rda/entity/attachment.rb +88 -0
- data/lib/discord_rda/entity/base.rb +103 -0
- data/lib/discord_rda/entity/channel.rb +446 -0
- data/lib/discord_rda/entity/channel_builder.rb +280 -0
- data/lib/discord_rda/entity/color.rb +253 -0
- data/lib/discord_rda/entity/embed.rb +221 -0
- data/lib/discord_rda/entity/emoji.rb +89 -0
- data/lib/discord_rda/entity/factory.rb +99 -0
- data/lib/discord_rda/entity/guild.rb +619 -0
- data/lib/discord_rda/entity/member.rb +263 -0
- data/lib/discord_rda/entity/message.rb +405 -0
- data/lib/discord_rda/entity/message_builder.rb +369 -0
- data/lib/discord_rda/entity/role.rb +157 -0
- data/lib/discord_rda/entity/support.rb +294 -0
- data/lib/discord_rda/entity/user.rb +231 -0
- data/lib/discord_rda/entity/value_objects.rb +263 -0
- data/lib/discord_rda/event/auto_moderation.rb +294 -0
- data/lib/discord_rda/event/base.rb +986 -0
- data/lib/discord_rda/event/bus.rb +225 -0
- data/lib/discord_rda/event/scheduled_event.rb +257 -0
- data/lib/discord_rda/hot_reload_manager.rb +303 -0
- data/lib/discord_rda/interactions/application_command.rb +436 -0
- data/lib/discord_rda/interactions/command_system.rb +484 -0
- data/lib/discord_rda/interactions/components.rb +464 -0
- data/lib/discord_rda/interactions/interaction.rb +553 -0
- data/lib/discord_rda/plugin/analytics_plugin.rb +528 -0
- data/lib/discord_rda/plugin/base.rb +190 -0
- data/lib/discord_rda/plugin/registry.rb +126 -0
- data/lib/discord_rda/version.rb +5 -0
- data/lib/discord_rda.rb +70 -0
- metadata +302 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DiscordRDA
|
|
4
|
+
# Represents a Discord color.
|
|
5
|
+
# Provides conversion between RGB, hex, and integer representations.
|
|
6
|
+
#
|
|
7
|
+
# @example Creating colors
|
|
8
|
+
# Color.new(0xFF5733) # From integer
|
|
9
|
+
# Color.from_rgb(255, 87, 51) # From RGB
|
|
10
|
+
# Color.from_hex('#FF5733') # From hex string
|
|
11
|
+
# Color.teal # Named color
|
|
12
|
+
#
|
|
13
|
+
class Color
|
|
14
|
+
# Default Discord colors
|
|
15
|
+
DEFAULTS = {
|
|
16
|
+
default: 0,
|
|
17
|
+
white: 0xFFFFFF,
|
|
18
|
+
aqua: 0x1ABC9C,
|
|
19
|
+
green: 0x57F287,
|
|
20
|
+
blue: 0x3498DB,
|
|
21
|
+
yellow: 0xFEE75C,
|
|
22
|
+
purple: 0x9B59B6,
|
|
23
|
+
luminous_vivid_pink: 0xE91E63,
|
|
24
|
+
gold: 0xF1C40F,
|
|
25
|
+
orange: 0xE67E22,
|
|
26
|
+
red: 0xED4245,
|
|
27
|
+
grey: 0x95A5A6,
|
|
28
|
+
navy: 0x34495E,
|
|
29
|
+
dark_aqua: 0x11806A,
|
|
30
|
+
dark_green: 0x1F8B4C,
|
|
31
|
+
dark_blue: 0x206694,
|
|
32
|
+
dark_purple: 0x71368A,
|
|
33
|
+
dark_vivid_pink: 0xAD1457,
|
|
34
|
+
dark_gold: 0xC27C0E,
|
|
35
|
+
dark_orange: 0xA84300,
|
|
36
|
+
dark_red: 0x992D22,
|
|
37
|
+
dark_grey: 0x979C9F,
|
|
38
|
+
darker_grey: 0x7F8C8D,
|
|
39
|
+
light_grey: 0xBCC0C0,
|
|
40
|
+
dark_navy: 0x2C3E50,
|
|
41
|
+
blurple: 0x5865F2,
|
|
42
|
+
greyple: 0x99AAB5,
|
|
43
|
+
dark_but_not_black: 0x2C2F33,
|
|
44
|
+
not_quite_black: 0x23272A
|
|
45
|
+
}.freeze
|
|
46
|
+
|
|
47
|
+
# @return [Integer] The color value
|
|
48
|
+
attr_reader :value
|
|
49
|
+
|
|
50
|
+
class << self
|
|
51
|
+
# Create from RGB values
|
|
52
|
+
# @param r [Integer] Red (0-255)
|
|
53
|
+
# @param g [Integer] Green (0-255)
|
|
54
|
+
# @param b [Integer] Blue (0-255)
|
|
55
|
+
# @return [Color] New color
|
|
56
|
+
def from_rgb(r, g, b)
|
|
57
|
+
new((r.to_i << 16) | (g.to_i << 8) | b.to_i)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Create from hex string
|
|
61
|
+
# @param hex [String] Hex string (#RRGGBB or RRGGBB)
|
|
62
|
+
# @return [Color] New color
|
|
63
|
+
def from_hex(hex)
|
|
64
|
+
hex = hex.to_s.delete_prefix('#')
|
|
65
|
+
new(hex.to_i(16))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Create from HSL values
|
|
69
|
+
# @param h [Float] Hue (0-360)
|
|
70
|
+
# @param s [Float] Saturation (0-1)
|
|
71
|
+
# @param l [Float] Lightness (0-1)
|
|
72
|
+
# @return [Color] New color
|
|
73
|
+
def from_hsl(h, s, l)
|
|
74
|
+
h = h.to_f / 360.0
|
|
75
|
+
s = s.to_f
|
|
76
|
+
l = l.to_f
|
|
77
|
+
|
|
78
|
+
r, g, b = if s == 0
|
|
79
|
+
[l, l, l]
|
|
80
|
+
else
|
|
81
|
+
q = l < 0.5 ? l * (1 + s) : l + s - l * s
|
|
82
|
+
p = 2 * l - q
|
|
83
|
+
[hue_to_rgb(p, q, h + 1.0 / 3),
|
|
84
|
+
hue_to_rgb(p, q, h),
|
|
85
|
+
hue_to_rgb(p, q, h - 1.0 / 3)]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
from_rgb((r * 255).round, (g * 255).round, (b * 255).round)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Generate random color
|
|
92
|
+
# @return [Color] Random color
|
|
93
|
+
def random
|
|
94
|
+
new(rand(0xFFFFFF))
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Named color methods
|
|
98
|
+
DEFAULTS.each do |name, value|
|
|
99
|
+
define_method(name) { new(value) }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
def hue_to_rgb(p, q, t)
|
|
105
|
+
t += 1 if t < 0
|
|
106
|
+
t -= 1 if t > 1
|
|
107
|
+
return p + (q - p) * 6 * t if t < 1.0 / 6
|
|
108
|
+
return q if t < 1.0 / 2
|
|
109
|
+
return p + (q - p) * (2.0 / 3 - t) * 6 if t < 2.0 / 3
|
|
110
|
+
p
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Initialize with a color value
|
|
115
|
+
# @param value [Integer] Color value (0-0xFFFFFF)
|
|
116
|
+
def initialize(value = 0)
|
|
117
|
+
@value = value.to_i & 0xFFFFFF
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Get red component
|
|
121
|
+
# @return [Integer] Red (0-255)
|
|
122
|
+
def r
|
|
123
|
+
(@value >> 16) & 0xFF
|
|
124
|
+
end
|
|
125
|
+
alias red r
|
|
126
|
+
|
|
127
|
+
# Get green component
|
|
128
|
+
# @return [Integer] Green (0-255)
|
|
129
|
+
def g
|
|
130
|
+
(@value >> 8) & 0xFF
|
|
131
|
+
end
|
|
132
|
+
alias green g
|
|
133
|
+
|
|
134
|
+
# Get blue component
|
|
135
|
+
# @return [Integer] Blue (0-255)
|
|
136
|
+
def b
|
|
137
|
+
@value & 0xFF
|
|
138
|
+
end
|
|
139
|
+
alias blue b
|
|
140
|
+
|
|
141
|
+
# Get RGB array
|
|
142
|
+
# @return [Array<Integer>] RGB values
|
|
143
|
+
def rgb
|
|
144
|
+
[r, g, b]
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Get hex string
|
|
148
|
+
# @param prefix [Boolean] Include # prefix
|
|
149
|
+
# @return [String] Hex string
|
|
150
|
+
def to_hex(prefix: true)
|
|
151
|
+
hex = @value.to_s(16).upcase.rjust(6, '0')
|
|
152
|
+
prefix ? "##{hex}" : hex
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Convert to RGB tuple string
|
|
156
|
+
# @return [String] RGB string
|
|
157
|
+
def to_rgb_string
|
|
158
|
+
"rgb(#{r}, #{g}, #{b})"
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Get integer value
|
|
162
|
+
# @return [Integer] Color value
|
|
163
|
+
def to_i
|
|
164
|
+
@value
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Convert to decimal color string (for Discord)
|
|
168
|
+
# @return [String] Decimal string
|
|
169
|
+
def to_s
|
|
170
|
+
@value.to_s
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Check if color is valid (non-zero)
|
|
174
|
+
# @return [Boolean] True if has color
|
|
175
|
+
def valid?
|
|
176
|
+
@value > 0
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Check if color is the default (0)
|
|
180
|
+
# @return [Boolean] True if default
|
|
181
|
+
def default?
|
|
182
|
+
@value == 0
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Get brightness (0-255)
|
|
186
|
+
# @return [Integer] Brightness
|
|
187
|
+
def brightness
|
|
188
|
+
(r * 299 + g * 587 + b * 114) / 1000
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Check if color is light (brightness > 128)
|
|
192
|
+
# @return [Boolean] True if light
|
|
193
|
+
def light?
|
|
194
|
+
brightness > 128
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Check if color is dark (brightness <= 128)
|
|
198
|
+
# @return [Boolean] True if dark
|
|
199
|
+
def dark?
|
|
200
|
+
brightness <= 128
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Blend with another color
|
|
204
|
+
# @param other [Color] Other color
|
|
205
|
+
# @param ratio [Float] Blend ratio (0-1)
|
|
206
|
+
# @return [Color] Blended color
|
|
207
|
+
def blend(other, ratio = 0.5)
|
|
208
|
+
r = (self.r * (1 - ratio) + other.r * ratio).round
|
|
209
|
+
g = (self.g * (1 - ratio) + other.g * ratio).round
|
|
210
|
+
b = (self.b * (1 - ratio) + other.b * ratio).round
|
|
211
|
+
self.class.from_rgb(r, g, b)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Darken the color
|
|
215
|
+
# @param amount [Float] Amount to darken (0-1)
|
|
216
|
+
# @return [Color] Darkened color
|
|
217
|
+
def darken(amount = 0.2)
|
|
218
|
+
blend(self.class.new(0), amount)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Lighten the color
|
|
222
|
+
# @param amount [Float] Amount to lighten (0-1)
|
|
223
|
+
# @return [Color] Lightened color
|
|
224
|
+
def lighten(amount = 0.2)
|
|
225
|
+
blend(self.class.new(0xFFFFFF), amount)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Get complementary color
|
|
229
|
+
# @return [Color] Complementary color
|
|
230
|
+
def complementary
|
|
231
|
+
self.class.from_rgb(255 - r, 255 - g, 255 - b)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Check equality
|
|
235
|
+
# @param other [Object] Other object
|
|
236
|
+
# @return [Boolean] True if equal
|
|
237
|
+
def ==(other)
|
|
238
|
+
other.is_a?(Color) && @value == other.value
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Hash code
|
|
242
|
+
# @return [Integer] Hash code
|
|
243
|
+
def hash
|
|
244
|
+
@value.hash
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Inspect
|
|
248
|
+
# @return [String] Inspect string
|
|
249
|
+
def inspect
|
|
250
|
+
"#<Color #{to_hex}>"
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DiscordRDA
|
|
4
|
+
# Represents a Discord message embed.
|
|
5
|
+
#
|
|
6
|
+
class Embed < Entity
|
|
7
|
+
# Embed types
|
|
8
|
+
TYPES = %w[rich image video gifv article link].freeze
|
|
9
|
+
|
|
10
|
+
attribute :title, type: :string
|
|
11
|
+
attribute :type, type: :string, default: 'rich'
|
|
12
|
+
attribute :description, type: :string
|
|
13
|
+
attribute :url, type: :string
|
|
14
|
+
attribute :timestamp, type: :time
|
|
15
|
+
attribute :color, type: :integer, default: 0
|
|
16
|
+
attribute :footer, type: :hash
|
|
17
|
+
attribute :image, type: :hash
|
|
18
|
+
attribute :thumbnail, type: :hash
|
|
19
|
+
attribute :video, type: :hash
|
|
20
|
+
attribute :provider, type: :hash
|
|
21
|
+
attribute :author, type: :hash
|
|
22
|
+
attribute :fields, type: :array, default: []
|
|
23
|
+
|
|
24
|
+
# Get color as Color object
|
|
25
|
+
# @return [Color] Embed color
|
|
26
|
+
def color_object
|
|
27
|
+
Color.new(color)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Check if embed has fields
|
|
31
|
+
# @return [Boolean] True if has fields
|
|
32
|
+
def has_fields?
|
|
33
|
+
fields.any?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Get fields
|
|
37
|
+
# @return [Array<EmbedField>] Fields
|
|
38
|
+
def embed_fields
|
|
39
|
+
(@raw_data['fields'] || []).map { |f| EmbedField.new(f) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Get footer
|
|
43
|
+
# @return [EmbedFooter, nil] Footer
|
|
44
|
+
def embed_footer
|
|
45
|
+
EmbedFooter.new(@raw_data['footer']) if @raw_data['footer']
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Get image
|
|
49
|
+
# @return [EmbedImage, nil] Image
|
|
50
|
+
def embed_image
|
|
51
|
+
EmbedImage.new(@raw_data['image']) if @raw_data['image']
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Get thumbnail
|
|
55
|
+
# @return [EmbedThumbnail, nil] Thumbnail
|
|
56
|
+
def embed_thumbnail
|
|
57
|
+
EmbedThumbnail.new(@raw_data['thumbnail']) if @raw_data['thumbnail']
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Get video
|
|
61
|
+
# @return [EmbedVideo, nil] Video
|
|
62
|
+
def embed_video
|
|
63
|
+
EmbedVideo.new(@raw_data['video']) if @raw_data['video']
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Get provider
|
|
67
|
+
# @return [EmbedProvider, nil] Provider
|
|
68
|
+
def embed_provider
|
|
69
|
+
EmbedProvider.new(@raw_data['provider']) if @raw_data['provider']
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Get author
|
|
73
|
+
# @return [EmbedAuthor, nil] Author
|
|
74
|
+
def embed_author
|
|
75
|
+
EmbedAuthor.new(@raw_data['author']) if @raw_data['author']
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Check if embed has image or thumbnail
|
|
79
|
+
# @return [Boolean] True if has media
|
|
80
|
+
def has_media?
|
|
81
|
+
@raw_data['image'] || @raw_data['thumbnail']
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Check if embed is empty
|
|
85
|
+
# @return [Boolean] True if empty
|
|
86
|
+
def empty?
|
|
87
|
+
title.nil? && description.nil? && !has_fields?
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Builder class for creating embeds
|
|
91
|
+
class Builder
|
|
92
|
+
def initialize
|
|
93
|
+
@data = {}
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def title(value)
|
|
97
|
+
@data['title'] = value
|
|
98
|
+
self
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def description(value)
|
|
102
|
+
@data['description'] = value
|
|
103
|
+
self
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def url(value)
|
|
107
|
+
@data['url'] = value
|
|
108
|
+
self
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def timestamp(value)
|
|
112
|
+
@data['timestamp'] = value.iso8601
|
|
113
|
+
self
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def color(value)
|
|
117
|
+
@data['color'] = value.is_a?(Color) ? value.to_i : value
|
|
118
|
+
self
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def field(name:, value:, inline: false)
|
|
122
|
+
@data['fields'] ||= []
|
|
123
|
+
@data['fields'] << { 'name' => name, 'value' => value, 'inline' => inline }
|
|
124
|
+
self
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def footer(text:, icon_url: nil)
|
|
128
|
+
@data['footer'] = { 'text' => text }
|
|
129
|
+
@data['footer']['icon_url'] = icon_url if icon_url
|
|
130
|
+
self
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def image(url:, proxy_url: nil, height: nil, width: nil)
|
|
134
|
+
@data['image'] = { 'url' => url }
|
|
135
|
+
@data['image']['proxy_url'] = proxy_url if proxy_url
|
|
136
|
+
@data['image']['height'] = height if height
|
|
137
|
+
@data['image']['width'] = width if width
|
|
138
|
+
self
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def thumbnail(url:, proxy_url: nil, height: nil, width: nil)
|
|
142
|
+
@data['thumbnail'] = { 'url' => url }
|
|
143
|
+
@data['thumbnail']['proxy_url'] = proxy_url if proxy_url
|
|
144
|
+
@data['thumbnail']['height'] = height if height
|
|
145
|
+
@data['thumbnail']['width'] = width if width
|
|
146
|
+
self
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def author(name:, url: nil, icon_url: nil)
|
|
150
|
+
@data['author'] = { 'name' => name }
|
|
151
|
+
@data['author']['url'] = url if url
|
|
152
|
+
@data['author']['icon_url'] = icon_url if icon_url
|
|
153
|
+
self
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def build
|
|
157
|
+
Embed.new(@data)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def to_h
|
|
161
|
+
@data
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Embed field
|
|
167
|
+
class EmbedField < Entity
|
|
168
|
+
attribute :name, type: :string
|
|
169
|
+
attribute :value, type: :string
|
|
170
|
+
attribute :inline, type: :boolean, default: false
|
|
171
|
+
|
|
172
|
+
def inline?
|
|
173
|
+
inline
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Embed footer
|
|
178
|
+
class EmbedFooter < Entity
|
|
179
|
+
attribute :text, type: :string
|
|
180
|
+
attribute :icon_url, type: :string
|
|
181
|
+
attribute :proxy_icon_url, type: :string
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Embed image
|
|
185
|
+
class EmbedImage < Entity
|
|
186
|
+
attribute :url, type: :string
|
|
187
|
+
attribute :proxy_url, type: :string
|
|
188
|
+
attribute :height, type: :integer
|
|
189
|
+
attribute :width, type: :integer
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Embed thumbnail
|
|
193
|
+
class EmbedThumbnail < Entity
|
|
194
|
+
attribute :url, type: :string
|
|
195
|
+
attribute :proxy_url, type: :string
|
|
196
|
+
attribute :height, type: :integer
|
|
197
|
+
attribute :width, type: :integer
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Embed video
|
|
201
|
+
class EmbedVideo < Entity
|
|
202
|
+
attribute :url, type: :string
|
|
203
|
+
attribute :proxy_url, type: :string
|
|
204
|
+
attribute :height, type: :integer
|
|
205
|
+
attribute :width, type: :integer
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Embed provider
|
|
209
|
+
class EmbedProvider < Entity
|
|
210
|
+
attribute :name, type: :string
|
|
211
|
+
attribute :url, type: :string
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Embed author
|
|
215
|
+
class EmbedAuthor < Entity
|
|
216
|
+
attribute :name, type: :string
|
|
217
|
+
attribute :url, type: :string
|
|
218
|
+
attribute :icon_url, type: :string
|
|
219
|
+
attribute :proxy_icon_url, type: :string
|
|
220
|
+
end
|
|
221
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DiscordRDA
|
|
4
|
+
# Represents a Discord emoji (custom or Unicode).
|
|
5
|
+
#
|
|
6
|
+
class Emoji < Entity
|
|
7
|
+
attribute :name, type: :string
|
|
8
|
+
attribute :roles, type: :array, default: []
|
|
9
|
+
attribute :user, type: :hash
|
|
10
|
+
attribute :require_colons, type: :boolean, default: true
|
|
11
|
+
attribute :managed, type: :boolean, default: false
|
|
12
|
+
attribute :animated, type: :boolean, default: false
|
|
13
|
+
attribute :available, type: :boolean, default: true
|
|
14
|
+
|
|
15
|
+
# Get the guild ID
|
|
16
|
+
# @return [Snowflake, nil] Guild ID
|
|
17
|
+
def guild_id
|
|
18
|
+
@raw_data['guild_id'] ? Snowflake.new(@raw_data['guild_id']) : nil
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Check if emoji is animated
|
|
22
|
+
# @return [Boolean] True if animated
|
|
23
|
+
def animated?
|
|
24
|
+
animated
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Check if emoji requires colons
|
|
28
|
+
# @return [Boolean] True if requires colons
|
|
29
|
+
def require_colons?
|
|
30
|
+
require_colons
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Check if emoji is managed by integration
|
|
34
|
+
# @return [Boolean] True if managed
|
|
35
|
+
def managed?
|
|
36
|
+
managed
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Check if emoji is available (not blocked by role restrictions)
|
|
40
|
+
# @return [Boolean] True if available
|
|
41
|
+
def available?
|
|
42
|
+
available
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Get the emoji creator
|
|
46
|
+
# @return [User, nil] Creator
|
|
47
|
+
def user
|
|
48
|
+
@user ||= User.new(@raw_data['user']) if @raw_data['user']
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Get role restrictions
|
|
52
|
+
# @return [Array<Snowflake>] Role IDs
|
|
53
|
+
def role_ids
|
|
54
|
+
(@raw_data['roles'] || []).map { |r| Snowflake.new(r) }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Get the emoji URL
|
|
58
|
+
# @return [String] Emoji URL
|
|
59
|
+
def url
|
|
60
|
+
ext = animated? ? 'gif' : 'png'
|
|
61
|
+
"https://cdn.discordapp.com/emojis/#{id}.#{ext}"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Get the emoji mention string
|
|
65
|
+
# @return [String] Emoji mention
|
|
66
|
+
def mention
|
|
67
|
+
prefix = animated? ? 'a' : ''
|
|
68
|
+
"<#{prefix}:#{name}:#{id}>"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Check if this is a Unicode emoji (no ID)
|
|
72
|
+
# @return [Boolean] True if Unicode emoji
|
|
73
|
+
def unicode?
|
|
74
|
+
id.nil?
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Check if this is a custom emoji
|
|
78
|
+
# @return [Boolean] True if custom emoji
|
|
79
|
+
def custom?
|
|
80
|
+
!id.nil?
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Get creation time (custom emojis only)
|
|
84
|
+
# @return [Time, nil] Creation time
|
|
85
|
+
def created_at
|
|
86
|
+
id&.timestamp
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DiscordRDA
|
|
4
|
+
# Factory for creating Discord entities from API data.
|
|
5
|
+
# Supports registration of custom entity types.
|
|
6
|
+
#
|
|
7
|
+
# @example Basic usage
|
|
8
|
+
# user = EntityFactory.create(:user, api_data)
|
|
9
|
+
# guild = EntityFactory.create(:guild, api_data)
|
|
10
|
+
#
|
|
11
|
+
# @example Custom entity registration
|
|
12
|
+
# class CustomGuild < Guild
|
|
13
|
+
# # custom implementation
|
|
14
|
+
# end
|
|
15
|
+
# EntityFactory.register(:guild, CustomGuild)
|
|
16
|
+
#
|
|
17
|
+
class EntityFactory
|
|
18
|
+
# Default entity mappings
|
|
19
|
+
DEFAULT_ENTITIES = {
|
|
20
|
+
user: 'DiscordRDA::User',
|
|
21
|
+
guild: 'DiscordRDA::Guild',
|
|
22
|
+
channel: 'DiscordRDA::Channel',
|
|
23
|
+
message: 'DiscordRDA::Message',
|
|
24
|
+
role: 'DiscordRDA::Role',
|
|
25
|
+
member: 'DiscordRDA::Member',
|
|
26
|
+
emoji: 'DiscordRDA::Emoji',
|
|
27
|
+
attachment: 'DiscordRDA::Attachment',
|
|
28
|
+
embed: 'DiscordRDA::Embed',
|
|
29
|
+
webhook: 'DiscordRDA::Webhook'
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
32
|
+
class << self
|
|
33
|
+
# Register an entity type
|
|
34
|
+
# @param type [Symbol] Entity type identifier
|
|
35
|
+
# @param klass [Class] Entity class
|
|
36
|
+
# @return [void]
|
|
37
|
+
def register(type, klass)
|
|
38
|
+
registry[type.to_sym] = klass
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Create an entity from data
|
|
42
|
+
# @param type [Symbol] Entity type
|
|
43
|
+
# @param data [Hash] API data
|
|
44
|
+
# @return [Entity] Created entity
|
|
45
|
+
def create(type, data)
|
|
46
|
+
klass = registry[type.to_sym]
|
|
47
|
+
raise ArgumentError, "Unknown entity type: #{type}" unless klass
|
|
48
|
+
|
|
49
|
+
klass.new(data)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Create multiple entities from array data
|
|
53
|
+
# @param type [Symbol] Entity type
|
|
54
|
+
# @param data_array [Array<Hash>] Array of API data
|
|
55
|
+
# @return [Array<Entity>] Created entities
|
|
56
|
+
def create_many(type, data_array)
|
|
57
|
+
return [] unless data_array.is_a?(Array)
|
|
58
|
+
|
|
59
|
+
data_array.map { |data| create(type, data) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Check if an entity type is registered
|
|
63
|
+
# @param type [Symbol] Entity type
|
|
64
|
+
# @return [Boolean] True if registered
|
|
65
|
+
def registered?(type)
|
|
66
|
+
registry.key?(type.to_sym)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Get all registered types
|
|
70
|
+
# @return [Array<Symbol>] Registered type names
|
|
71
|
+
def registered_types
|
|
72
|
+
registry.keys
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Unregister an entity type
|
|
76
|
+
# @param type [Symbol] Entity type to unregister
|
|
77
|
+
# @return [void]
|
|
78
|
+
def unregister(type)
|
|
79
|
+
registry.delete(type.to_sym)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Reset to default registrations
|
|
83
|
+
# @return [void]
|
|
84
|
+
def reset!
|
|
85
|
+
@registry = nil
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def registry
|
|
91
|
+
@registry ||= build_default_registry
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def build_default_registry
|
|
95
|
+
DEFAULT_ENTITIES.transform_values { |class_name| Object.const_get(class_name) }
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|