rubord 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 +39 -0
- data/lib/rubord/components/actionRow.rb +93 -0
- data/lib/rubord/components/button.rb +125 -0
- data/lib/rubord/components/componentsV2.rb +4 -0
- data/lib/rubord/components/containers/base.rb +15 -0
- data/lib/rubord/components/containers/container.rb +39 -0
- data/lib/rubord/components/containers/section.rb +26 -0
- data/lib/rubord/components/containers/separator.rb +51 -0
- data/lib/rubord/components/containers/text.rb +23 -0
- data/lib/rubord/components/modal.rb +134 -0
- data/lib/rubord/components/select_menu.rb +147 -0
- data/lib/rubord/models/channel.rb +50 -0
- data/lib/rubord/models/collection.rb +70 -0
- data/lib/rubord/models/commands/base.rb +111 -0
- data/lib/rubord/models/commands/command.rb +3 -0
- data/lib/rubord/models/commands/loader.rb +36 -0
- data/lib/rubord/models/commands/registry.rb +26 -0
- data/lib/rubord/models/components.rb +5 -0
- data/lib/rubord/models/embed.rb +87 -0
- data/lib/rubord/models/flags.rb +249 -0
- data/lib/rubord/models/guild.rb +78 -0
- data/lib/rubord/models/interaction.rb +136 -0
- data/lib/rubord/models/member.rb +63 -0
- data/lib/rubord/models/mention.rb +47 -0
- data/lib/rubord/models/message.rb +88 -0
- data/lib/rubord/models/role.rb +15 -0
- data/lib/rubord/models/user.rb +21 -0
- data/lib/rubord/structs/client.rb +364 -0
- data/lib/rubord/structs/gateway.rb +363 -0
- data/lib/rubord/structs/logger.rb +19 -0
- data/lib/rubord/structs/models.rb +19 -0
- data/lib/rubord/structs/parser.rb +68 -0
- data/lib/rubord/structs/rate_limiter.rb +163 -0
- data/lib/rubord/structs/rest.rb +353 -0
- data/lib/rubord.rb +8 -0
- metadata +105 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# components/select_menu.rb
|
|
2
|
+
|
|
3
|
+
module Rubord
|
|
4
|
+
# Represents a Discord select menu component.
|
|
5
|
+
#
|
|
6
|
+
# Select menus are dropdown components that allow users to select
|
|
7
|
+
# one or multiple options from a list.
|
|
8
|
+
#
|
|
9
|
+
# @example Creating a single-select menu
|
|
10
|
+
# menu = Rubord::SelectMenu.new(
|
|
11
|
+
# custom_id: "game_selection",
|
|
12
|
+
# placeholder: "Choose a game..."
|
|
13
|
+
# )
|
|
14
|
+
# menu.add_option(label: "Minecraft", value: "minecraft")
|
|
15
|
+
# menu.add_option(label: "Terraria", value: "terraria")
|
|
16
|
+
#
|
|
17
|
+
# @example Creating a multi-select menu
|
|
18
|
+
# menu = Rubord::SelectMenu.new(
|
|
19
|
+
# custom_id: "hobbies",
|
|
20
|
+
# placeholder: "Select your hobbies",
|
|
21
|
+
# min_values: 1,
|
|
22
|
+
# max_values: 3
|
|
23
|
+
# )
|
|
24
|
+
#
|
|
25
|
+
# @since 1.0.0
|
|
26
|
+
# @see https://discord.com/developers/docs/interactions/message-components#select-menus
|
|
27
|
+
class SelectMenu
|
|
28
|
+
# @return [Integer] Component type (always 3 for select menus).
|
|
29
|
+
attr_accessor :type
|
|
30
|
+
|
|
31
|
+
# @return [String] Developer-defined identifier.
|
|
32
|
+
attr_accessor :custom_id
|
|
33
|
+
|
|
34
|
+
# @return [Array<Hash>] List of available options.
|
|
35
|
+
attr_accessor :options
|
|
36
|
+
|
|
37
|
+
# @return [String, nil] Placeholder text displayed when no option is selected.
|
|
38
|
+
attr_accessor :placeholder
|
|
39
|
+
|
|
40
|
+
# @return [Integer] Minimum number of options that must be selected.
|
|
41
|
+
# Defaults to 1.
|
|
42
|
+
attr_accessor :min_values
|
|
43
|
+
|
|
44
|
+
# @return [Integer] Maximum number of options that can be selected.
|
|
45
|
+
# Defaults to 1.
|
|
46
|
+
attr_accessor :max_values
|
|
47
|
+
|
|
48
|
+
# @return [Boolean] Whether the select menu is disabled.
|
|
49
|
+
attr_accessor :disabled
|
|
50
|
+
|
|
51
|
+
# Creates a new select menu component.
|
|
52
|
+
#
|
|
53
|
+
# @param custom_id [String] Developer-defined identifier.
|
|
54
|
+
# @param placeholder [String, nil] Placeholder text.
|
|
55
|
+
# @param min_values [Integer] Minimum selectable options.
|
|
56
|
+
# Defaults to 1.
|
|
57
|
+
# @param max_values [Integer] Maximum selectable options.
|
|
58
|
+
# Defaults to 1.
|
|
59
|
+
# @param disabled [Boolean] Whether the menu is disabled.
|
|
60
|
+
# Defaults to false.
|
|
61
|
+
#
|
|
62
|
+
# @example Basic select menu
|
|
63
|
+
# SelectMenu.new(
|
|
64
|
+
# custom_id: "color_picker",
|
|
65
|
+
# placeholder: "Choose a color",
|
|
66
|
+
# min_values: 1,
|
|
67
|
+
# max_values: 1
|
|
68
|
+
# )
|
|
69
|
+
#
|
|
70
|
+
# @example Multi-select menu
|
|
71
|
+
# SelectMenu.new(
|
|
72
|
+
# custom_id: "tags",
|
|
73
|
+
# placeholder: "Select up to 3 tags",
|
|
74
|
+
# min_values: 0,
|
|
75
|
+
# max_values: 3
|
|
76
|
+
# )
|
|
77
|
+
def initialize(custom_id:, placeholder: nil, min_values: 1, max_values: 1, disabled: false)
|
|
78
|
+
@type = 3
|
|
79
|
+
@custom_id = custom_id
|
|
80
|
+
@options = []
|
|
81
|
+
@placeholder = placeholder
|
|
82
|
+
@min_values = min_values
|
|
83
|
+
@max_values = max_values
|
|
84
|
+
@disabled = disabled
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Adds an option to the select menu.
|
|
88
|
+
#
|
|
89
|
+
# @param label [String] The user-facing name of the option (max 100 chars).
|
|
90
|
+
# @param value [String] The developer-defined value of the option (max 100 chars).
|
|
91
|
+
# @param description [String, nil] Additional description (max 100 chars).
|
|
92
|
+
# @param emoji [Hash, nil] Emoji to display with the option.
|
|
93
|
+
# @param default [Boolean] Whether this option is selected by default.
|
|
94
|
+
# Defaults to false.
|
|
95
|
+
#
|
|
96
|
+
# @return [Rubord::SelectMenu] Self for method chaining.
|
|
97
|
+
#
|
|
98
|
+
# @example Adding an option with description
|
|
99
|
+
# menu.add_option(
|
|
100
|
+
# label: "Ruby",
|
|
101
|
+
# value: "ruby_lang",
|
|
102
|
+
# description: "Dynamic, object-oriented programming language",
|
|
103
|
+
# emoji: { name: "💎" }
|
|
104
|
+
# )
|
|
105
|
+
def add_option(label:, value:, description: nil, emoji: nil, default: false)
|
|
106
|
+
@options << {
|
|
107
|
+
label: label,
|
|
108
|
+
value: value,
|
|
109
|
+
description: description,
|
|
110
|
+
emoji: emoji,
|
|
111
|
+
default: default
|
|
112
|
+
}.compact
|
|
113
|
+
self
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Converts the select menu to a Discord API-compatible hash.
|
|
117
|
+
#
|
|
118
|
+
# @return [Hash] Select menu data in Discord API format.
|
|
119
|
+
#
|
|
120
|
+
# @example
|
|
121
|
+
# menu = SelectMenu.new(custom_id: "test")
|
|
122
|
+
# menu.to_h
|
|
123
|
+
# # => {type: 3, custom_id: "test", options: [], min_values: 1, max_values: 1, disabled: false}
|
|
124
|
+
def to_h
|
|
125
|
+
{
|
|
126
|
+
type: @type,
|
|
127
|
+
custom_id: @custom_id,
|
|
128
|
+
options: @options,
|
|
129
|
+
placeholder: @placeholder,
|
|
130
|
+
min_values: @min_values,
|
|
131
|
+
max_values: @max_values,
|
|
132
|
+
disabled: @disabled
|
|
133
|
+
}.compact
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Factory method for creating SelectMenu instances.
|
|
138
|
+
#
|
|
139
|
+
# @param args [Hash] Select menu initialization parameters.
|
|
140
|
+
# @return [Rubord::SelectMenu] New select menu instance.
|
|
141
|
+
#
|
|
142
|
+
# @example
|
|
143
|
+
# menu = Rubord.SelectMenu(custom_id: "choices", placeholder: "Make a choice")
|
|
144
|
+
def self.SelectMenu(**args)
|
|
145
|
+
SelectMenu.new(**args)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module Rubord
|
|
2
|
+
class Channel
|
|
3
|
+
attr_reader :id,
|
|
4
|
+
:name,
|
|
5
|
+
:type,
|
|
6
|
+
:client
|
|
7
|
+
|
|
8
|
+
def initialize(data, client)
|
|
9
|
+
@id = data["id"]
|
|
10
|
+
@name = data["name"]
|
|
11
|
+
@type = data["type"]
|
|
12
|
+
@client = client
|
|
13
|
+
|
|
14
|
+
client&.channels&.set(@id, self)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def post(content = nil, embeds: nil, components: nil, flags: nil)
|
|
18
|
+
ensure_client!
|
|
19
|
+
|
|
20
|
+
client.rest.send_message(
|
|
21
|
+
@id,
|
|
22
|
+
content: content,
|
|
23
|
+
embeds: embeds,
|
|
24
|
+
components: components,
|
|
25
|
+
flags: flags
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def fetch_message(message_id)
|
|
30
|
+
client.rest.get_message(@id, message_id)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def guild
|
|
34
|
+
return nil unless client && client.respond_to?(:guilds)
|
|
35
|
+
client.guilds.get(guild_id)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def text?
|
|
39
|
+
type == 0
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def voice?
|
|
43
|
+
type == 2
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def inspect
|
|
47
|
+
"#<Rubord::Channel id=#{id} name=#{name.inspect} type=#{type}>"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
module Rubord
|
|
2
|
+
class Collection
|
|
3
|
+
include Enumerable
|
|
4
|
+
|
|
5
|
+
def initialize
|
|
6
|
+
@store = {}
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def [](key)
|
|
10
|
+
@store[key]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def []=(key, value)
|
|
14
|
+
@store[key] = value
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def get(key)
|
|
18
|
+
@store[key]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def set(key, value)
|
|
22
|
+
@store[key] = value
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def delete(key)
|
|
27
|
+
@store.delete(key)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def each(&block)
|
|
31
|
+
@store.each_value(&block)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def map(&block)
|
|
35
|
+
@store.values.map(&block)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def filter(&block)
|
|
39
|
+
self.class.new.tap do |col|
|
|
40
|
+
@store.each do |k, v|
|
|
41
|
+
col.set(k, v) if block.call(v)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def find(&block)
|
|
47
|
+
@store.values.find(&block)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def values
|
|
51
|
+
@store.values
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def keys
|
|
55
|
+
@store.keys
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def size
|
|
59
|
+
@store.size
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def empty?
|
|
63
|
+
@store.empty?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def to_h
|
|
67
|
+
@store.dup
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
module Rubord
|
|
2
|
+
# Base class for creating commands.
|
|
3
|
+
#
|
|
4
|
+
# @example Creating a simple command
|
|
5
|
+
# class PingCommand < Rubord::CommandBase
|
|
6
|
+
# name "ping"
|
|
7
|
+
# description "Responds with pong"
|
|
8
|
+
#
|
|
9
|
+
# # @yieldparam message [Rubord::Message] The message that triggered the command
|
|
10
|
+
# def run(message)
|
|
11
|
+
# message.reply("Pong!")
|
|
12
|
+
# end
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# @abstract
|
|
16
|
+
class CommandBase
|
|
17
|
+
class << self
|
|
18
|
+
attr_reader :command_name,
|
|
19
|
+
:description_text,
|
|
20
|
+
:command_usage,
|
|
21
|
+
:aliases_list,
|
|
22
|
+
:cooldown_seconds,
|
|
23
|
+
:guild_only_flag,
|
|
24
|
+
:dm_only_flag
|
|
25
|
+
|
|
26
|
+
def inherited(subclass)
|
|
27
|
+
super
|
|
28
|
+
subclass.instance_variable_set(:@aliases_list, [])
|
|
29
|
+
subclass.instance_variable_set(:@cooldown_seconds, 0)
|
|
30
|
+
subclass.instance_variable_set(:@guild_only_flag, false)
|
|
31
|
+
subclass.instance_variable_set(:@dm_only_flag, false)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def name(value)
|
|
35
|
+
@command_name = value.to_s
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def description(value)
|
|
39
|
+
@description_text = value.to_s
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def aliases(*values)
|
|
43
|
+
@aliases_list.concat(values.map(&:to_s))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def cooldown(seconds)
|
|
47
|
+
@cooldown_seconds = seconds.to_i
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def guild_only(value = true)
|
|
51
|
+
@guild_only_flag = value
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def dm_only(value = true)
|
|
55
|
+
@dm_only_flag = value
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def validate!
|
|
59
|
+
raise ArgumentError, "Command #{self} is missing a name" unless @command_name
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
attr_reader :client
|
|
64
|
+
|
|
65
|
+
def initialize(client)
|
|
66
|
+
@client = client
|
|
67
|
+
self.class.validate!
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @return [String]
|
|
71
|
+
def name
|
|
72
|
+
self.class.command_name
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @return [String]
|
|
76
|
+
def description
|
|
77
|
+
self.class.description_text
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @return [Array<String>]
|
|
81
|
+
def aliases
|
|
82
|
+
self.class.aliases_list
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @return [Integer]
|
|
86
|
+
def cooldown
|
|
87
|
+
self.class.cooldown_seconds
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def guild_only?
|
|
91
|
+
self.class.guild_only_flag
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def dm_only?
|
|
95
|
+
self.class.dm_only_flag
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Executes the command.
|
|
99
|
+
#
|
|
100
|
+
# @param message [Rubord::Message]
|
|
101
|
+
# @param args [Array<String>]
|
|
102
|
+
# @abstract
|
|
103
|
+
def run(_message, _args = [])
|
|
104
|
+
raise NotImplementedError, "#{self.class} must implement #run(message)"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def inspect
|
|
108
|
+
"#<#{self.class} name=#{command_name.inspect}>"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# loader.rb
|
|
2
|
+
module Rubord
|
|
3
|
+
class CommandLoader
|
|
4
|
+
def self.load(path, client, registry)
|
|
5
|
+
full_path = File.expand_path(path)
|
|
6
|
+
files = Dir["#{full_path}/**/*.rb"]
|
|
7
|
+
|
|
8
|
+
files.each do |file|
|
|
9
|
+
begin
|
|
10
|
+
Kernel.load(file)
|
|
11
|
+
rescue => e
|
|
12
|
+
puts "[Rubord:Commands] ERRO ao carregar #{file}:"
|
|
13
|
+
puts " #{e.class}: #{e.message}"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
found_classes = []
|
|
18
|
+
ObjectSpace.each_object(Class) do |klass|
|
|
19
|
+
next if klass == Rubord::CommandBase
|
|
20
|
+
|
|
21
|
+
if klass.ancestors.include?(Rubord::CommandBase)
|
|
22
|
+
found_classes << klass
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
found_classes.each do |klass|
|
|
27
|
+
begin
|
|
28
|
+
registry.register(klass, client)
|
|
29
|
+
rescue => e
|
|
30
|
+
puts "[Rubord:Commands] ERRO ao registrar #{klass}:"
|
|
31
|
+
puts " #{e.class}: #{e.message}"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Rubord
|
|
2
|
+
class CommandRegistry
|
|
3
|
+
def initialize
|
|
4
|
+
@commands = {}
|
|
5
|
+
@command_classes = {}
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def register(klass, client)
|
|
9
|
+
name = klass.command_name
|
|
10
|
+
raise "Command missing name" unless name
|
|
11
|
+
|
|
12
|
+
@command_classes[name] = klass
|
|
13
|
+
@commands[name] = klass.new(client)
|
|
14
|
+
|
|
15
|
+
Rubord::Logger.success "[Rubord:Commands] Registered command: #{name}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def get(name)
|
|
19
|
+
@commands[name]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def all
|
|
23
|
+
@commands.values.freeze
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
module Rubord
|
|
2
|
+
class Embed
|
|
3
|
+
attr_accessor :title,
|
|
4
|
+
:description,
|
|
5
|
+
:url,
|
|
6
|
+
:timestamp,
|
|
7
|
+
:color,
|
|
8
|
+
:footer,
|
|
9
|
+
:image,
|
|
10
|
+
:thumbnail,
|
|
11
|
+
:author,
|
|
12
|
+
:fields
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@fields = []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def title(text)
|
|
19
|
+
@title = text
|
|
20
|
+
self
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def description(text)
|
|
24
|
+
@description = text
|
|
25
|
+
self
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def url(link)
|
|
29
|
+
@url = link
|
|
30
|
+
self
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def timestamp(time = Time.now)
|
|
34
|
+
@timestamp = time.utc.iso8601
|
|
35
|
+
self
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def color(hex)
|
|
39
|
+
@color = Rubord.Parser.color(hex)
|
|
40
|
+
self
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def footer(text:, icon_url: nil)
|
|
44
|
+
@footer = { text: text, icon_url: icon_url }
|
|
45
|
+
self
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def image(url)
|
|
49
|
+
@image = { url: url }
|
|
50
|
+
self
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def thumbnail(url)
|
|
54
|
+
@thumbnail = { url: url }
|
|
55
|
+
self
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def author(name:, url: nil, icon_url: nil)
|
|
59
|
+
@author = { name: name, url: url, icon_url: icon_url }
|
|
60
|
+
self
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def add_field(name, value, inline = false)
|
|
64
|
+
@fields << { name: name, value: value, inline: inline }
|
|
65
|
+
self
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def to_h
|
|
69
|
+
{
|
|
70
|
+
title: @title,
|
|
71
|
+
description: @description,
|
|
72
|
+
url: @url,
|
|
73
|
+
timestamp: @timestamp,
|
|
74
|
+
color: @color,
|
|
75
|
+
footer: @footer,
|
|
76
|
+
image: @image,
|
|
77
|
+
thumbnail: @thumbnail,
|
|
78
|
+
author: @author,
|
|
79
|
+
fields: @fields.empty? ? nil : @fields
|
|
80
|
+
}.compact
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def self.Embed
|
|
85
|
+
Embed.new
|
|
86
|
+
end
|
|
87
|
+
end
|