onyxcord 1.1.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.
- checksums.yaml +7 -0
- data/.devcontainer/Dockerfile +13 -0
- data/.devcontainer/devcontainer.json +29 -0
- data/.devcontainer/postcreate.sh +4 -0
- data/.github/CONTRIBUTING.md +13 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
- data/.github/pull_request_template.md +37 -0
- data/.github/workflows/ci.yml +78 -0
- data/.github/workflows/codeql.yml +65 -0
- data/.github/workflows/deploy.yml +54 -0
- data/.github/workflows/release.yml +51 -0
- data/.gitignore +16 -0
- data/.markdownlint.json +4 -0
- data/.overcommit.yml +7 -0
- data/.rspec +2 -0
- data/.rubocop.yml +129 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +0 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +305 -0
- data/Rakefile +17 -0
- data/bin/console +15 -0
- data/bin/setup +7 -0
- data/lib/onyxcord/allowed_mentions.rb +43 -0
- data/lib/onyxcord/api/application.rb +316 -0
- data/lib/onyxcord/api/channel.rb +700 -0
- data/lib/onyxcord/api/interaction.rb +67 -0
- data/lib/onyxcord/api/invite.rb +44 -0
- data/lib/onyxcord/api/server.rb +775 -0
- data/lib/onyxcord/api/user.rb +158 -0
- data/lib/onyxcord/api/webhook.rb +163 -0
- data/lib/onyxcord/api.rb +335 -0
- data/lib/onyxcord/await.rb +51 -0
- data/lib/onyxcord/bot.rb +1971 -0
- data/lib/onyxcord/cache.rb +326 -0
- data/lib/onyxcord/colour_rgb.rb +43 -0
- data/lib/onyxcord/commands/command_bot.rb +511 -0
- data/lib/onyxcord/commands/container.rb +112 -0
- data/lib/onyxcord/commands/events.rb +11 -0
- data/lib/onyxcord/commands/parser.rb +327 -0
- data/lib/onyxcord/commands/rate_limiter.rb +144 -0
- data/lib/onyxcord/configuration.rb +125 -0
- data/lib/onyxcord/container.rb +988 -0
- data/lib/onyxcord/data/activity.rb +271 -0
- data/lib/onyxcord/data/application.rb +341 -0
- data/lib/onyxcord/data/attachment.rb +91 -0
- data/lib/onyxcord/data/audit_logs.rb +438 -0
- data/lib/onyxcord/data/avatar_decoration.rb +26 -0
- data/lib/onyxcord/data/call.rb +22 -0
- data/lib/onyxcord/data/channel.rb +1355 -0
- data/lib/onyxcord/data/channel_tag.rb +69 -0
- data/lib/onyxcord/data/collectibles.rb +47 -0
- data/lib/onyxcord/data/component.rb +583 -0
- data/lib/onyxcord/data/embed.rb +258 -0
- data/lib/onyxcord/data/emoji.rb +123 -0
- data/lib/onyxcord/data/install_params.rb +24 -0
- data/lib/onyxcord/data/integration.rb +144 -0
- data/lib/onyxcord/data/interaction.rb +1141 -0
- data/lib/onyxcord/data/invite.rb +137 -0
- data/lib/onyxcord/data/member.rb +528 -0
- data/lib/onyxcord/data/message.rb +612 -0
- data/lib/onyxcord/data/message_activity.rb +41 -0
- data/lib/onyxcord/data/overwrite.rb +109 -0
- data/lib/onyxcord/data/poll.rb +365 -0
- data/lib/onyxcord/data/primary_server.rb +60 -0
- data/lib/onyxcord/data/profile.rb +79 -0
- data/lib/onyxcord/data/reaction.rb +64 -0
- data/lib/onyxcord/data/recipient.rb +34 -0
- data/lib/onyxcord/data/role.rb +449 -0
- data/lib/onyxcord/data/role_connection_data.rb +69 -0
- data/lib/onyxcord/data/role_subscription.rb +41 -0
- data/lib/onyxcord/data/scheduled_event.rb +513 -0
- data/lib/onyxcord/data/server.rb +1614 -0
- data/lib/onyxcord/data/server_preview.rb +68 -0
- data/lib/onyxcord/data/snapshot.rb +112 -0
- data/lib/onyxcord/data/team.rb +98 -0
- data/lib/onyxcord/data/timestamp.rb +69 -0
- data/lib/onyxcord/data/user.rb +324 -0
- data/lib/onyxcord/data/voice_region.rb +46 -0
- data/lib/onyxcord/data/voice_state.rb +41 -0
- data/lib/onyxcord/data/webhook.rb +238 -0
- data/lib/onyxcord/data.rb +57 -0
- data/lib/onyxcord/errors.rb +246 -0
- data/lib/onyxcord/event_executor.rb +80 -0
- data/lib/onyxcord/events/await.rb +48 -0
- data/lib/onyxcord/events/bans.rb +60 -0
- data/lib/onyxcord/events/channels.rb +225 -0
- data/lib/onyxcord/events/generic.rb +129 -0
- data/lib/onyxcord/events/guilds.rb +269 -0
- data/lib/onyxcord/events/integrations.rb +100 -0
- data/lib/onyxcord/events/interactions.rb +624 -0
- data/lib/onyxcord/events/invites.rb +127 -0
- data/lib/onyxcord/events/lifetime.rb +31 -0
- data/lib/onyxcord/events/members.rb +110 -0
- data/lib/onyxcord/events/message.rb +399 -0
- data/lib/onyxcord/events/polls.rb +118 -0
- data/lib/onyxcord/events/presence.rb +131 -0
- data/lib/onyxcord/events/raw.rb +74 -0
- data/lib/onyxcord/events/reactions.rb +218 -0
- data/lib/onyxcord/events/roles.rb +87 -0
- data/lib/onyxcord/events/scheduled_events.rb +171 -0
- data/lib/onyxcord/events/threads.rb +100 -0
- data/lib/onyxcord/events/typing.rb +73 -0
- data/lib/onyxcord/events/voice_server_update.rb +48 -0
- data/lib/onyxcord/events/voice_state_update.rb +106 -0
- data/lib/onyxcord/events/webhooks.rb +65 -0
- data/lib/onyxcord/gateway.rb +890 -0
- data/lib/onyxcord/id_object.rb +39 -0
- data/lib/onyxcord/light/data.rb +62 -0
- data/lib/onyxcord/light/integrations.rb +73 -0
- data/lib/onyxcord/light/light_bot.rb +58 -0
- data/lib/onyxcord/light.rb +8 -0
- data/lib/onyxcord/logger.rb +120 -0
- data/lib/onyxcord/message_components.rb +70 -0
- data/lib/onyxcord/paginator.rb +60 -0
- data/lib/onyxcord/permissions.rb +255 -0
- data/lib/onyxcord/rate_limiter/gateway.rb +42 -0
- data/lib/onyxcord/rate_limiter/rest.rb +89 -0
- data/lib/onyxcord/version.rb +7 -0
- data/lib/onyxcord/voice/encoder.rb +115 -0
- data/lib/onyxcord/voice/network.rb +380 -0
- data/lib/onyxcord/voice/opcodes.rb +29 -0
- data/lib/onyxcord/voice/sodium.rb +157 -0
- data/lib/onyxcord/voice/timer.rb +19 -0
- data/lib/onyxcord/voice/voice_bot.rb +386 -0
- data/lib/onyxcord/webhooks.rb +14 -0
- data/lib/onyxcord/websocket.rb +62 -0
- data/lib/onyxcord.rb +180 -0
- data/onyxcord-webhooks.gemspec +30 -0
- data/onyxcord.gemspec +50 -0
- metadata +421 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OnyxCord
|
|
4
|
+
# A permissions overwrite, when applied to channels describes additional
|
|
5
|
+
# permissions a member needs to perform certain actions in context.
|
|
6
|
+
class Overwrite
|
|
7
|
+
# Types of overwrites mapped to their API value.
|
|
8
|
+
TYPES = {
|
|
9
|
+
role: 0,
|
|
10
|
+
member: 1
|
|
11
|
+
}.freeze
|
|
12
|
+
|
|
13
|
+
# @return [Integer] ID of the thing associated with this overwrite type
|
|
14
|
+
attr_accessor :id
|
|
15
|
+
|
|
16
|
+
# @return [Symbol] either :role or :member
|
|
17
|
+
attr_accessor :type
|
|
18
|
+
|
|
19
|
+
# @return [Permissions] allowed permissions for this overwrite type
|
|
20
|
+
attr_accessor :allow
|
|
21
|
+
|
|
22
|
+
# @return [Permissions] denied permissions for this overwrite type
|
|
23
|
+
attr_accessor :deny
|
|
24
|
+
|
|
25
|
+
# Creates a new Overwrite object
|
|
26
|
+
# @example Create an overwrite for a role that can mention everyone, send TTS messages, but can't create instant invites
|
|
27
|
+
# allow = OnyxCord::Permissions.new
|
|
28
|
+
# allow.can_mention_everyone = true
|
|
29
|
+
# allow.can_send_tts_messages = true
|
|
30
|
+
#
|
|
31
|
+
# deny = OnyxCord::Permissions.new
|
|
32
|
+
# deny.can_create_instant_invite = true
|
|
33
|
+
#
|
|
34
|
+
# # Find some role by name
|
|
35
|
+
# role = server.roles.find { |r| r.name == 'some role' }
|
|
36
|
+
#
|
|
37
|
+
# Overwrite.new(role, allow: allow, deny: deny)
|
|
38
|
+
# @example Create an overwrite by ID and permissions bits
|
|
39
|
+
# Overwrite.new(120571255635181568, type: 'member', allow: 1024, deny: 0)
|
|
40
|
+
# @param object [Integer, #id] the ID or object this overwrite is for
|
|
41
|
+
# @param type [String, Symbol, Integer] the type of object this overwrite is for (only required if object is an Integer)
|
|
42
|
+
# @param allow [String, Integer, Permissions] allowed permissions for this overwrite, by bits or a Permissions object
|
|
43
|
+
# @param deny [String, Integer, Permissions] denied permissions for this overwrite, by bits or a Permissions object
|
|
44
|
+
# @raise [ArgumentError] if type is not :member or :role
|
|
45
|
+
def initialize(object = nil, type: nil, allow: 0, deny: 0)
|
|
46
|
+
if type
|
|
47
|
+
type = TYPES.value?(type) ? TYPES.key(type) : type.to_sym
|
|
48
|
+
raise ArgumentError, 'Overwrite type must be :member or :role' unless type
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
@id = object.respond_to?(:id) ? object.id : object
|
|
52
|
+
|
|
53
|
+
@type = case object
|
|
54
|
+
when User, Member, Recipient, Profile
|
|
55
|
+
:member
|
|
56
|
+
when Role
|
|
57
|
+
:role
|
|
58
|
+
else
|
|
59
|
+
type
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
@allow = allow.is_a?(Permissions) ? allow : Permissions.new(allow)
|
|
63
|
+
@deny = deny.is_a?(Permissions) ? deny : Permissions.new(deny)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Comparison by attributes [:id, :type, :allow, :deny]
|
|
67
|
+
def ==(other)
|
|
68
|
+
return false unless other.is_a?(OnyxCord::Overwrite)
|
|
69
|
+
|
|
70
|
+
id == other.id &&
|
|
71
|
+
type == other.type &&
|
|
72
|
+
allow == other.allow &&
|
|
73
|
+
deny == other.deny
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @return [Overwrite] create an overwrite from a hash payload
|
|
77
|
+
# @!visibility private
|
|
78
|
+
def self.from_hash(data)
|
|
79
|
+
new(
|
|
80
|
+
data['id'].to_i,
|
|
81
|
+
type: TYPES.key(data['type']),
|
|
82
|
+
allow: Permissions.new(data['allow']),
|
|
83
|
+
deny: Permissions.new(data['deny'])
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @return [Overwrite] copies an overwrite from another Overwrite
|
|
88
|
+
# @!visibility private
|
|
89
|
+
def self.from_other(other)
|
|
90
|
+
new(
|
|
91
|
+
other.id,
|
|
92
|
+
type: other.type,
|
|
93
|
+
allow: Permissions.new(other.allow.bits),
|
|
94
|
+
deny: Permissions.new(other.deny.bits)
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @return [Hash] hash representation of an overwrite
|
|
99
|
+
# @!visibility private
|
|
100
|
+
def to_hash
|
|
101
|
+
{
|
|
102
|
+
id: id,
|
|
103
|
+
type: TYPES[type],
|
|
104
|
+
allow: allow.bits,
|
|
105
|
+
deny: deny.bits
|
|
106
|
+
}
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OnyxCord
|
|
4
|
+
# A surface with selectable answers.
|
|
5
|
+
class Poll
|
|
6
|
+
# Mapping of layout types for polls.
|
|
7
|
+
LAYOUTS = {
|
|
8
|
+
default: 1
|
|
9
|
+
}.freeze
|
|
10
|
+
|
|
11
|
+
# @return [Integer] the layout type of the poll.
|
|
12
|
+
attr_reader :layout
|
|
13
|
+
|
|
14
|
+
# @return [Array<Answer>] the answers of the poll.
|
|
15
|
+
attr_reader :answers
|
|
16
|
+
|
|
17
|
+
# @return [Message] the message linked to the poll.
|
|
18
|
+
attr_reader :message
|
|
19
|
+
|
|
20
|
+
# @return [Media] the display-data for the poll question.
|
|
21
|
+
attr_reader :question
|
|
22
|
+
|
|
23
|
+
# @return [Time, nil] the time at when the poll will expire.
|
|
24
|
+
attr_reader :closes_at
|
|
25
|
+
|
|
26
|
+
# @return [true, false] whether or not Discord has precisely counted the votes yet.
|
|
27
|
+
attr_reader :finalised
|
|
28
|
+
alias finalised? finalised
|
|
29
|
+
alias finalized? finalised
|
|
30
|
+
|
|
31
|
+
# @return [true, false] whether or not users are allowed to vote for multiple answers.
|
|
32
|
+
attr_reader :multiselect
|
|
33
|
+
alias multiselect? multiselect
|
|
34
|
+
|
|
35
|
+
# @return [true, false] whether or not Discord did not fetch the results for the poll.
|
|
36
|
+
# When this is `true`, the {#finalised?} method will always return a value of `false`,
|
|
37
|
+
# and {Answer#votes} will always return a value of `0`.
|
|
38
|
+
attr_reader :unknown_results
|
|
39
|
+
alias unknown_results? unknown_results
|
|
40
|
+
|
|
41
|
+
# @!visibility private
|
|
42
|
+
def initialize(data, message, bot)
|
|
43
|
+
@bot = bot
|
|
44
|
+
@message = message
|
|
45
|
+
@layout = data['layout_type']
|
|
46
|
+
@question = Media.new(data['question'], @bot)
|
|
47
|
+
@multiselect = data['allow_multiselect']
|
|
48
|
+
@closes_at = Time.parse(data['expiry']) if data['expiry']
|
|
49
|
+
results = data['results']
|
|
50
|
+
@unknown_results = results.nil?
|
|
51
|
+
@finalised = results&.[]('is_finalized') || false
|
|
52
|
+
process_answers(data['answers'], results&.[]('answer_counts'))
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Get the total amount of votes cast on the poll.
|
|
56
|
+
# @return [Integer] The total amount of votes that the poll received.
|
|
57
|
+
def total_votes
|
|
58
|
+
@answers.sum(&:votes)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Get a single answer for the poll by its ID.
|
|
62
|
+
# @param answer_id [Integer, String, Answer] The ID of the poll answer.
|
|
63
|
+
# @return [Answer, nil] The poll answer, or `nil` if it couldn't be found.
|
|
64
|
+
def answer(answer_id)
|
|
65
|
+
answer_id = if answer_id.is_a?(Answer)
|
|
66
|
+
answer_id.id
|
|
67
|
+
else
|
|
68
|
+
answer_id.resolve_id
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
@answers.find { |answer| answer.id == answer_id }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Check if the poll has closed.
|
|
75
|
+
# @return [true, false] Whether or not the poll has closed.
|
|
76
|
+
def closed?
|
|
77
|
+
@finalised || (!@closes_at.nil? && Time.now > @closes_at)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Get the poll answer that has the most amount of votes.
|
|
81
|
+
# @return [Answer, nil] The winning poll answer, or `nil` for no winner.
|
|
82
|
+
def winner
|
|
83
|
+
return unless (max = @answers.max_by(&:votes))&.votes&.nonzero?
|
|
84
|
+
|
|
85
|
+
@answers.one? { |answer| answer.votes == max&.votes } ? max : nil
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Prematurely end the poll, only functional for polls created by the current bot.
|
|
89
|
+
# @return [Message] The resulting message. Will fail if the poll was not sent by the current bot.
|
|
90
|
+
# @raise [OnyxCord::Errors::NoPermission] If the poll was not created by the current bot account.
|
|
91
|
+
def close
|
|
92
|
+
raise OnyxCord::Errors::NoPermission, 'Cannot close the poll' if !@message.from_bot? || closed?
|
|
93
|
+
|
|
94
|
+
Message.new(JSON.parse(API::Channel.end_poll(@bot.token, @message.channel.id, @message.id)), @bot)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Check if two poll objects are equivalent.
|
|
98
|
+
# @param other [Object] The object to compare the poll object against.
|
|
99
|
+
# @return [true, false] Whether or not the poll objects are equivalent.
|
|
100
|
+
def ==(other)
|
|
101
|
+
other.is_a?(Poll) ? @message == other.message : false
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
alias_method :eql?, :==
|
|
105
|
+
|
|
106
|
+
# @!method default_layout?
|
|
107
|
+
# @return [true, false] whether or not the poll is using the default layout.
|
|
108
|
+
LAYOUTS.each do |name, value|
|
|
109
|
+
define_method("#{name}_layout?") { @layout == value }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# @!visibility private
|
|
113
|
+
def inspect
|
|
114
|
+
"<Poll question=\"#{@question.text}\" unknown_results=#{@unknown_results}>"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# @!visibility private
|
|
118
|
+
def to_h
|
|
119
|
+
{
|
|
120
|
+
layout_type: @layout,
|
|
121
|
+
question: @question.to_h,
|
|
122
|
+
answers: @answers.map(&:to_h),
|
|
123
|
+
allow_multiselect: @multiselect,
|
|
124
|
+
duration: @closes_at ? ((@closes_at - @message.creation_time) / 3600).round : nil
|
|
125
|
+
}
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
private
|
|
129
|
+
|
|
130
|
+
# @!visibility private
|
|
131
|
+
def process_answers(answers, counts)
|
|
132
|
+
@answers = answers.map do |answer|
|
|
133
|
+
count_data = counts&.find { |count| count['id'] == answer['answer_id'] }
|
|
134
|
+
|
|
135
|
+
Answer.new(answer.tap { answer['_votes'] = count_data&.[]('count') }, self, @bot)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# A selectable poll answer.
|
|
140
|
+
class Answer
|
|
141
|
+
# @return [Integer] the ID of the poll answer.
|
|
142
|
+
attr_reader :id
|
|
143
|
+
|
|
144
|
+
# @return [Integer] the number of votes the answer has.
|
|
145
|
+
attr_reader :votes
|
|
146
|
+
alias vote_count votes
|
|
147
|
+
|
|
148
|
+
# @return [Integer] the ID of the message the answer is from.
|
|
149
|
+
attr_reader :message_id
|
|
150
|
+
|
|
151
|
+
# @!visibility private
|
|
152
|
+
def initialize(data, poll, bot)
|
|
153
|
+
@bot = bot
|
|
154
|
+
@poll = poll
|
|
155
|
+
@id = data['answer_id']
|
|
156
|
+
@votes = data['_votes'] || 0
|
|
157
|
+
@media = Media.new(data['poll_media'], @bot)
|
|
158
|
+
@channel_id = data['_channel_id'] unless @poll
|
|
159
|
+
@message_id = @poll&.message&.id || data['_message_id']&.to_i
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Get the text of the poll answer.
|
|
163
|
+
# @return [String] The text of the poll answer.
|
|
164
|
+
def text
|
|
165
|
+
@media.text
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Get the emoji of the poll answer.
|
|
169
|
+
# @return [Emoji, nil] The emoji of the poll answer.
|
|
170
|
+
def emoji
|
|
171
|
+
@media.emoji
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Check if the poll answer won the poll.
|
|
175
|
+
# @return [true, false] Whether or not the poll answer is the winner.
|
|
176
|
+
def winner?
|
|
177
|
+
@poll.nil? || @poll.winner&.id == @id
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Get the users who voted for the poll answer.
|
|
181
|
+
# @param after [Time, #resolve_id, nil] The ID or timestamp to start fetching voters from.
|
|
182
|
+
# @param limit [Integer, nil] The maximum number of voters to fetch, or `nil` to fetch all the voters.
|
|
183
|
+
# @return [Array<User>] The users who voted for the poll answer; ordered by user ID in ascending order.
|
|
184
|
+
def voters(after: nil, limit: 100)
|
|
185
|
+
stable = @poll.nil? || @poll.finalised?
|
|
186
|
+
channel_id = @channel_id || @poll.message.channel.id
|
|
187
|
+
after_time = after.is_a?(Time) ? IDObject.synthesise(after) : after&.resolve_id
|
|
188
|
+
|
|
189
|
+
get_users = lambda do |limit, after|
|
|
190
|
+
data = API::Channel.get_poll_voters(@bot.token, channel_id, @message_id, @id, after:, limit:)
|
|
191
|
+
JSON.parse(data)['users'].collect { |poll_voter_data| @bot.ensure_user(poll_voter_data) }
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
return get_users.call(limit, after_time) if limit && limit <= 100
|
|
195
|
+
|
|
196
|
+
paginator = Paginator.new(limit, :down) do |last_page|
|
|
197
|
+
if stable && last_page && last_page.count < 100
|
|
198
|
+
[]
|
|
199
|
+
else
|
|
200
|
+
get_users.call(100, last_page&.last&.id || after_time)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
paginator.to_a
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Check if two poll answers are equivalent.
|
|
208
|
+
# @param other [Object] The object to compare the poll answer against.
|
|
209
|
+
# @return [true, false] Whether or not the poll answers are equivalent.
|
|
210
|
+
def ==(other)
|
|
211
|
+
return false unless other.is_a?(Answer)
|
|
212
|
+
|
|
213
|
+
@message_id == other.message_id && @id == other.id
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
alias_method :eql?, :==
|
|
217
|
+
|
|
218
|
+
# @!visibility private
|
|
219
|
+
def to_h
|
|
220
|
+
{ poll_media: @media.to_h }
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# @!visibility private
|
|
224
|
+
def inspect
|
|
225
|
+
"<Answer id=#{@id} text=\"#{text}\" votes=#{@votes}>"
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Represents display-data for a poll.
|
|
230
|
+
class Media
|
|
231
|
+
# @return [String, nil] the text of the poll media.
|
|
232
|
+
attr_reader :text
|
|
233
|
+
|
|
234
|
+
# @return [Emoji, nil] the emoji of the poll media.
|
|
235
|
+
attr_reader :emoji
|
|
236
|
+
|
|
237
|
+
# @!visibility private
|
|
238
|
+
def initialize(data, bot)
|
|
239
|
+
@bot = bot
|
|
240
|
+
@text = data['text']
|
|
241
|
+
@emoji = Emoji.new(data['emoji'], @bot) if data['emoji']
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Check if two poll media objects are equivalent.
|
|
245
|
+
# @param other [Object] The object to compare the poll media against.
|
|
246
|
+
# @return [true, false] Whether or not the poll media objects are equivalent.
|
|
247
|
+
def ==(other)
|
|
248
|
+
return false unless other.is_a?(Media)
|
|
249
|
+
|
|
250
|
+
@text == other.text && @emoji == other.emoji
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
alias_method :eql?, :==
|
|
254
|
+
|
|
255
|
+
# @!visibility private
|
|
256
|
+
def to_h
|
|
257
|
+
{ text: @text, emoji: @emoji&.to_h }.compact
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# @!visibility private
|
|
261
|
+
def inspect
|
|
262
|
+
"<Media text=\"#{@text}\" emoji=#{@emoji.inspect}>"
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Finalised results for a poll.
|
|
267
|
+
class Result
|
|
268
|
+
# @return [Answer, nil] the answer that won the poll, if any.
|
|
269
|
+
attr_reader :winner
|
|
270
|
+
alias answer winner
|
|
271
|
+
|
|
272
|
+
# @return [Media] the display-data for the question of the poll.
|
|
273
|
+
attr_reader :question
|
|
274
|
+
|
|
275
|
+
# @return [Integer] the ID of the message the poll result is for.
|
|
276
|
+
attr_reader :message_id
|
|
277
|
+
|
|
278
|
+
# @return [Integer] the total amount of votes that the poll received.
|
|
279
|
+
attr_reader :total_votes
|
|
280
|
+
|
|
281
|
+
# @!visibility private
|
|
282
|
+
def initialize(embed, reference, bot)
|
|
283
|
+
@bot = bot
|
|
284
|
+
@message_id = reference['message_id'].to_i
|
|
285
|
+
embed = embed['fields'].to_h { |field| [field['name'], field['value']] }
|
|
286
|
+
@question = Media.new({ 'text' => embed['poll_question_text'] }, @bot)
|
|
287
|
+
@total_votes = embed['total_votes'].to_i
|
|
288
|
+
return unless (id = embed['victor_answer_id']&.to_i)
|
|
289
|
+
|
|
290
|
+
data = {
|
|
291
|
+
'poll_media' => {
|
|
292
|
+
'text' => embed['victor_answer_text']
|
|
293
|
+
},
|
|
294
|
+
'answer_id' => id,
|
|
295
|
+
'_message_id' => @message_id,
|
|
296
|
+
'_channel_id' => reference['channel_id'],
|
|
297
|
+
'_votes' => embed['victor_answer_votes'].to_i
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if embed['victor_answer_emoji_id'] || embed['victor_answer_emoji_name']
|
|
301
|
+
data['poll_media']['emoji'] = {
|
|
302
|
+
'name' => embed['victor_answer_emoji_name'],
|
|
303
|
+
'id' => embed['victor_answer_emoji_id']&.to_i,
|
|
304
|
+
'animated' => embed['victor_answer_emoji_animated'] == 'true'
|
|
305
|
+
}
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
@winner = Answer.new(data, nil, @bot)
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# Check if two result objects are equivalent.
|
|
312
|
+
# @param other [Object] The object to compare the result object against.
|
|
313
|
+
# @return [true, false] Whether or not the result objects are equivalent.
|
|
314
|
+
def ==(other)
|
|
315
|
+
other.is_a?(Result) ? @message_id == other.message_id : false
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
alias_method :eql?, :==
|
|
319
|
+
|
|
320
|
+
# @!visibility private
|
|
321
|
+
def inspect
|
|
322
|
+
"<Result winner=#{@winner.inspect} total_votes=#{@total_votes}>"
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# Builder for polls.
|
|
327
|
+
class Builder
|
|
328
|
+
# Create a poll request object.
|
|
329
|
+
# @param question [String] The question of the poll; between 1-55 characters.
|
|
330
|
+
# @param layout [Integer, Symbol, nil] The layout type of the poll; see {LAYOUTS}.
|
|
331
|
+
# @param duration [Integer, Time, nil] The number of hours before the poll expires.
|
|
332
|
+
# @param multiselect [true, false, nil] Whether or not users can pick multiple answers.
|
|
333
|
+
def initialize(question:, layout: :default, duration: 24, multiselect: false)
|
|
334
|
+
@layout = layout
|
|
335
|
+
@answers = []
|
|
336
|
+
@question = question
|
|
337
|
+
@duration = duration || 24
|
|
338
|
+
@multiselect = multiselect
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# Add an answer to the poll builder.
|
|
342
|
+
# @param text [String] The text of the poll answer.
|
|
343
|
+
# @param emoji [Emoji, Reaction, Integer, String, nil] The emoji of the poll answer.
|
|
344
|
+
# @return [void]
|
|
345
|
+
def answer(text:, emoji: nil)
|
|
346
|
+
emoji = Emoji.build_emoji_hash(emoji, prefix: false) if emoji
|
|
347
|
+
|
|
348
|
+
@answers << { poll_media: { text: text, emoji: emoji }.compact }
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
alias_method :add_answer, :answer
|
|
352
|
+
|
|
353
|
+
# @!visibility private
|
|
354
|
+
def to_h
|
|
355
|
+
{
|
|
356
|
+
question: { text: @question },
|
|
357
|
+
answers: @answers,
|
|
358
|
+
layout_type: LAYOUTS[@layout] || @layout,
|
|
359
|
+
allow_multiselect: @multiselect,
|
|
360
|
+
duration: @duration.is_a?(Time) ? ((@duration - Time.now) / 3600).round : @duration
|
|
361
|
+
}
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OnyxCord
|
|
4
|
+
# A server tag that a user has chosen to display on their profile.
|
|
5
|
+
class PrimaryServer
|
|
6
|
+
# @return [Integer] the ID of the server this primary server is for.
|
|
7
|
+
attr_reader :server_id
|
|
8
|
+
|
|
9
|
+
# @return [true, false] if the user is displaying the primary server's tag.
|
|
10
|
+
attr_reader :enabled
|
|
11
|
+
alias_method :enabled?, :enabled
|
|
12
|
+
|
|
13
|
+
# @return [String] the text of the primary server's tag. Limited to four characters.
|
|
14
|
+
attr_reader :name
|
|
15
|
+
alias_method :text, :name
|
|
16
|
+
|
|
17
|
+
# @return [String] the ID of the server tag's badge. Can be used to generate a badge URL.
|
|
18
|
+
# @see #badge_url
|
|
19
|
+
attr_reader :badge_id
|
|
20
|
+
|
|
21
|
+
# @!visibility private
|
|
22
|
+
def initialize(data, bot)
|
|
23
|
+
@bot = bot
|
|
24
|
+
@server_id = data['identity_guild_id']&.to_i
|
|
25
|
+
@enabled = data['identity_enabled']
|
|
26
|
+
@name = data['tag']
|
|
27
|
+
@badge_id = data['badge']
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Get the server associated with this primary server.
|
|
31
|
+
# @return [Server] the server associated with this primary server.
|
|
32
|
+
# @raise [OnyxCord::Errors::NoPermission] this can happen when the bot is not in the associated server.
|
|
33
|
+
def server
|
|
34
|
+
@bot.server(@server_id)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Get the server preview associated with this primary server.
|
|
38
|
+
# @return [ServerPreview, nil] the server preview associated with this primary server, or `nil` if it can't be accessed.
|
|
39
|
+
def server_preview
|
|
40
|
+
@bot.server_preview(@server_id)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Utility method to get a server tag's badge URL.
|
|
44
|
+
# @param format [String] the URL will default to `webp`. You can otherwise specify one of `jpg` or `png` to override this.
|
|
45
|
+
# @return [String] the URL to the server tag's badge image.
|
|
46
|
+
def badge_url(format = 'webp')
|
|
47
|
+
API.server_tag_badge_url(@server_id, @badge_id, format)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Comparison based off of server ID.
|
|
51
|
+
# @return [true, false] if the other object is equal to this primary server.
|
|
52
|
+
def ==(other)
|
|
53
|
+
return false unless other.is_a?(PrimaryServer)
|
|
54
|
+
|
|
55
|
+
OnyxCord.id_compare?(other.server_id, @server_id)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
alias_method :eql?, :==
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OnyxCord
|
|
4
|
+
# This class is a special variant of User that represents the bot's user profile (things like own username and the avatar).
|
|
5
|
+
# It can be accessed using {Bot#profile}.
|
|
6
|
+
class Profile < User
|
|
7
|
+
# Whether or not the user is the bot. The Profile can only ever be the bot user, so this always returns true.
|
|
8
|
+
# @return [true]
|
|
9
|
+
def current_bot?
|
|
10
|
+
true
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Sets the bot's username.
|
|
14
|
+
# @param username [String] The new username.
|
|
15
|
+
def username=(username)
|
|
16
|
+
update_profile_data(username: username)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
alias_method :name=, :username=
|
|
20
|
+
|
|
21
|
+
# Changes the bot's avatar.
|
|
22
|
+
# @param avatar [String, File, #read, nil] A file to be used as the avatar, either
|
|
23
|
+
# something readable (e.g. File Object) or a data URI.
|
|
24
|
+
def avatar=(avatar)
|
|
25
|
+
if avatar.respond_to?(:read)
|
|
26
|
+
update_profile_data(avatar: OnyxCord.encode64(avatar))
|
|
27
|
+
else
|
|
28
|
+
update_profile_data(avatar: avatar)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Changes the bot's banner.
|
|
33
|
+
# @param banner [String, File, #read, nil] A file to be used as the banner, either
|
|
34
|
+
# something readable (e.g. File Object) or a data URI.
|
|
35
|
+
def banner=(banner)
|
|
36
|
+
if banner.respond_to?(:read)
|
|
37
|
+
update_profile_data(banner: OnyxCord.encode64(banner))
|
|
38
|
+
else
|
|
39
|
+
update_profile_data(banner: banner)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Get the bot's global bio.
|
|
44
|
+
# @return [String] The bot's global bio, or an empty string if it doesn't have one set.
|
|
45
|
+
def bio
|
|
46
|
+
@bot.application.description
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Set the bot's global bio.
|
|
50
|
+
# @param bio [String, nil] The bot's new global bio, or `nil` to remove the current bio.
|
|
51
|
+
def bio=(bio)
|
|
52
|
+
@bot.application.modify(description: bio)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Updates the cached profile data with the new one.
|
|
56
|
+
# @note For internal use only.
|
|
57
|
+
# @!visibility private
|
|
58
|
+
def update_data(new_data)
|
|
59
|
+
@username = new_data['username']
|
|
60
|
+
@avatar_id = new_data['avatar']
|
|
61
|
+
@banner_id = new_data['banner']
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# The inspect method is overwritten to give more useful output
|
|
65
|
+
def inspect
|
|
66
|
+
"<Profile user=#{super}>"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
# @!visibility private
|
|
72
|
+
def update_profile_data(new_data)
|
|
73
|
+
update_data(JSON.parse(API::User.update_current_user(@bot.token,
|
|
74
|
+
new_data[:username] || :undef,
|
|
75
|
+
new_data.key?(:avatar) ? new_data[:avatar] : :undef,
|
|
76
|
+
new_data.key?(:banner) ? new_data[:banner] : :undef)))
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OnyxCord
|
|
4
|
+
# A reaction to a message.
|
|
5
|
+
class Reaction
|
|
6
|
+
# Map of reaction types.
|
|
7
|
+
TYPES = {
|
|
8
|
+
normal: 0,
|
|
9
|
+
burst: 1
|
|
10
|
+
}.freeze
|
|
11
|
+
|
|
12
|
+
# @return [Integer] the total amount of users who have reacted with this reaction (including burst reactions)
|
|
13
|
+
attr_reader :count
|
|
14
|
+
|
|
15
|
+
# @return [true, false] whether the current bot or user used this reaction
|
|
16
|
+
attr_reader :me
|
|
17
|
+
alias_method :me?, :me
|
|
18
|
+
|
|
19
|
+
# @return [Integer] the ID of the emoji, if it was custom
|
|
20
|
+
attr_reader :id
|
|
21
|
+
|
|
22
|
+
# @return [String] the name or unicode representation of the emoji
|
|
23
|
+
attr_reader :name
|
|
24
|
+
|
|
25
|
+
# @return [true, false] whether the current bot or user used this reaction as a burst reaction
|
|
26
|
+
attr_reader :me_burst
|
|
27
|
+
alias_method :me_burst?, :me_burst
|
|
28
|
+
|
|
29
|
+
# @return [Array<ColourRGB>] an array of colors used for animations in burst reactions
|
|
30
|
+
attr_reader :burst_colours
|
|
31
|
+
alias_method :burst_colors, :burst_colours
|
|
32
|
+
|
|
33
|
+
# @return [Integer] the total amount of users who have reacted with this reaction as a burst reaction
|
|
34
|
+
attr_reader :burst_count
|
|
35
|
+
|
|
36
|
+
# @return [Integer] the total amount of users who have reacted with this reaction as a normal reaction
|
|
37
|
+
attr_reader :normal_count
|
|
38
|
+
|
|
39
|
+
# @!visibility private
|
|
40
|
+
def initialize(data)
|
|
41
|
+
@count = data['count']
|
|
42
|
+
@me = data['me']
|
|
43
|
+
@id = data['emoji']['id']&.to_i
|
|
44
|
+
@name = data['emoji']['name']
|
|
45
|
+
@me_burst = data['me_burst']
|
|
46
|
+
@burst_colours = data['burst_colors'].map { |b| ColourRGB.new(b.delete('#')) }
|
|
47
|
+
@burst_count = data['count_details']['burst']
|
|
48
|
+
@normal_count = data['count_details']['normal']
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Converts this Reaction into a string that can be sent back to Discord in other reaction endpoints.
|
|
52
|
+
# If ID is present, it will be rendered into the form of `name:id`.
|
|
53
|
+
# @return [String] the name of this reaction, including the ID if it is a custom emoji
|
|
54
|
+
def to_s
|
|
55
|
+
id.nil? ? name : "#{name}:#{id}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Converts this Reaction into a hash that can be sent back to Discord in other endpoints.
|
|
59
|
+
# @return [Hash] A hash representation of this reaction's emoji.
|
|
60
|
+
def to_h
|
|
61
|
+
id.nil? ? { name: name } : { id: id }
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|