Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions lib/discordrb/api/channel.rb
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,61 @@ def unpin_message(token, channel_id, message_id, reason = nil)
)
end

# Fetch a pre-exisiting stage instance.
# https://discord.com/developers/docs/resources/stage-instance#get-stage-instance
def get_stage_instance(token, channel_id)
Discordrb::API.request(
:stage_instances_cid,
channel_id,
:get,
"#{Discordrb::API.api_base}/stage-instances/#{channel_id}",
Authorization: token
)
end

# Create a stage instance in a stage channel.
# https://discord.com/developers/docs/resources/stage-instance#create-stage-instance
def create_stage_instance(token, channel_id, topic: :undef, send_start_notification: :undef, guild_scheduled_event_id: :undef, privacy_level: :undef, reason: nil)
Discordrb::API.request(
:stage_instances,
channel_id,
:post,
"#{Discordrb::API.api_base}/stage-instances",
{ channel_id:, topic:, send_start_notification:, guild_scheduled_event_id:, privacy_level: }.reject { |_, value| value == :undef }.to_json,
content_type: :json,
Authorization: token,
'X-Audit-Log-Reason': reason
)
end

# Update a pre-exisiting stage instance.
# https://discord.com/developers/docs/resources/stage-instance#modify-stage-instance
def update_stage_instance(token, channel_id, topic: :undef, privacy_level: :undef, reason: nil)
Discordrb::API.request(
:stage_instances_cid,
channel_id,
:patch,
"#{Discordrb::API.api_base}/stage-instances/#{channel_id}",
{ topic:, privacy_level: }.reject { |_, value| value == :undef }.to_json,
content_type: :json,
Authorization: token,
'X-Audit-Log-Reason': reason
)
end

# Delete a pre-exisiting stage instance.
# https://discord.com/developers/docs/resources/stage-instance#delete-stage-instance
def delete_stage_instance(token, channel_id, reason: nil)
Discordrb::API.request(
:stage_instances_cid,
channel_id,
:delete,
"#{Discordrb::API.api_base}/stage-instances/#{channel_id}",
Authorization: token,
'X-Audit-Log-Reason': reason
)
end

# Create an empty group channel.
# @deprecated Discord no longer supports bots in group DMs, this endpoint was repurposed and no longer works as implemented here.
# https://discord.com/developers/docs/resources/user#create-group-dm
Expand Down
28 changes: 28 additions & 0 deletions lib/discordrb/bot.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
require 'discordrb/events/integrations'
require 'discordrb/events/scheduled_events'
require 'discordrb/events/polls'
require 'discordrb/events/stage_instances'

require 'discordrb/api'
require 'discordrb/api/channel'
Expand Down Expand Up @@ -1226,6 +1227,18 @@ def update_guild_scheduled_event(data)
end
end

# Internal handler for STAGE_INSTANCE_CREATE and STAGE_INSTANCE_UPDATE
def update_stage_instance(data)
channel = @channels[data['channel_id'].to_i]
instance = channel&.stage_instance(request: false)

if instance&.id == data['id'].to_i
instance&.update_data(data)
else
channel&.process_stage_instance(StageInstance.new(data, channel, self))
end
end

# Internal handler for MESSAGE_CREATE
def create_message(data); end

Expand Down Expand Up @@ -1756,6 +1769,21 @@ def handle_dispatch(type, data)

event = ThreadMembersUpdateEvent.new(data, self)
raise_event(event)
when :STAGE_INSTANCE_CREATE
update_stage_instance(data)

event = StageInstanceCreateEvent.new(data, self)
raise_event(event)
when :STAGE_INSTANCE_UPDATE
update_stage_instance(data)

event = StageInstanceUpdateEvent.new(data, self)
raise_event(event)
when :STAGE_INSTANCE_DELETE
@channels[data['channel_id'].to_i]&.process_stage_instance(nil)

event = StageInstanceDeleteEvent.new(data, self)
raise_event(event)
when :MESSAGE_POLL_VOTE_ADD
event = PollVoteAddEvent.new(data, self)
raise_event(event)
Expand Down
40 changes: 40 additions & 0 deletions lib/discordrb/container.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
require 'discordrb/events/integrations'
require 'discordrb/events/scheduled_events'
require 'discordrb/events/polls'
require 'discordrb/events/stage_instances'

require 'discordrb/await'

Expand Down Expand Up @@ -290,6 +291,45 @@ def channel_recipient_remove(attributes = {}, &block)
register_event(ChannelRecipientRemoveEvent, attributes, block)
end

# This **event** is raised whenever a stage instance is created.
# @param attributes [Hash] The event's attributes.
# @option attributes [String, Regexp] :topic Matches the topic of the stage instance.
# @option attributes [Integer, String, Server] :server Matches the server of the stage instance.
# @option attributes [Integer, String, Channel] :channel Matches the channel of the stage instance.
# @option attributes [Integer, String, ScheduledEvent] :scheduled_event Matches the scheduled event of the stage instance.
# @yield The block is executed when the event is raised.
# @yieldparam event [StageInstanceCreateEvent] The event that was raised.
# @return [StageInstanceCreateEventHandler] the event handler that was registered.
def stage_instance_create(attributes = {}, &block)
register_event(StageInstanceCreateEvent, attributes, block)
end

# This **event** is raised whenever a stage instance is updated.
# @param attributes [Hash] The event's attributes.
# @option attributes [String, Regexp] :topic Matches the topic of the stage instance.
# @option attributes [Integer, String, Server] :server Matches the server of the stage instance.
# @option attributes [Integer, String, Channel] :channel Matches the channel of the stage instance.
# @option attributes [Integer, String, ScheduledEvent] :scheduled_event Matches the scheduled event of the stage instance.
# @yield The block is executed when the event is raised.
# @yieldparam event [StageInstanceUpdateEvent] The event that was raised.
# @return [StageInstanceUpdateEventHandler] the event handler that was registered.
def stage_instance_update(attributes = {}, &block)
register_event(StageInstanceUpdateEvent, attributes, block)
end

# This **event** is raised whenever a stage instance is deleted.
# @param attributes [Hash] The event's attributes.
# @option attributes [String, Regexp] :topic Matches the topic of the stage instance.
# @option attributes [Integer, String, Server] :server Matches the server of the stage instance.
# @option attributes [Integer, String, Channel] :channel Matches the channel of the stage instance.
# @option attributes [Integer, String, ScheduledEvent] :scheduled_event Matches the scheduled event of the stage instance.
# @yield The block is executed when the event is raised.
# @yieldparam event [StageInstanceDeleteEvent] The event that was raised.
# @return [StageInstanceDeleteEventHandler] the event handler that was registered.
def stage_instance_delete(attributes = {}, &block)
register_event(StageInstanceDeleteEvent, attributes, block)
end

# This **event** is raised when a user's voice state changes. This includes when a user joins, leaves, or
# moves between voice channels, as well as their mute and deaf status for themselves and on the server.
# @param attributes [Hash] The event's attributes.
Expand Down
1 change: 1 addition & 0 deletions lib/discordrb/data.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,4 @@
require 'discordrb/data/timestamp'
require 'discordrb/data/scheduled_event'
require 'discordrb/data/poll'
require 'discordrb/data/stage_instance'
42 changes: 42 additions & 0 deletions lib/discordrb/data/channel.rb
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,40 @@ def slowmode?
@rate_limit_per_user != 0
end

# Get the stage instance for a stage channel.
# @param request [true, false] Whether to make an API call to fetch the stage instance if it isn't cached.
# @return [StageInstance, nil] The stage instance for this stage channel, or `nil` if one couldn't be found.
def stage_instance(request: false)
raise 'Channel must be a stage channel' unless stage?
return @stage_instance if @stage_instance || !request

instance = JSON.parse(API::Channel.get_stage_instance(@bot.token, @id))
process_stage_instance(StageInstance.new(instance, self, @bot))
rescue StandardError
nil
end

# Create a stage instance in a stage channel.
# @param topic [String] The 1-120 character topic of the stage instance.
# @param mention_everyone [true, false] Whether to ping @everyone when the stage instance starts.
# @param scheduled_event [ScheduledEvent, nil] The scheduled event to associate with the stage instance.
# @param reason [String, nil] The reason to show in the server's audit log for creating the stage instance.
# @return [StageInstance] The stage instance that was created.
def create_stage_instance(topic:, mention_everyone:, scheduled_event: nil, reason: nil)
raise 'Channel must be a stage channel' unless stage?

options = {
topic: topic,
reason: reason,
privacy_level: 2,
send_start_notification: mention_everyone,
guild_scheduled_event_id: scheduled_event&.resolve_id || :undef
}

instance = JSON.parse(API::Channel.create_stage_instance(@bot.token, @id, **options))
process_stage_instance(StageInstance.new(instance, self, @bot))
end

# Sends a message to this channel.
# @param content [String] The content to send. Should not be longer than 2000 characters or it will result in an error.
# @param tts [true, false] Whether or not this message should be sent using Discord text-to-speech.
Expand Down Expand Up @@ -1229,6 +1263,14 @@ def process_last_message_id(id)
@last_message_id = id
end

# Set the stage instance of a channel.
# @param instance [StageInstance, nil] the stage instance of the channel
# @note For internal use only
# @!visibility private
def process_stage_instance(instance)
@stage_instance = instance
end

# Set the available tags of a channel.
# @param tag [Hash] the data for the tag to create
# @param reason [String, nil] the reason to show in the audit log
Expand Down
10 changes: 10 additions & 0 deletions lib/discordrb/data/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1378,6 +1378,7 @@ def update_data(new_data = nil)
process_active_threads(new_data['threads']) if new_data['threads']
process_incident_actions(new_data['incidents_data']) if new_data.key?('incidents_data')
process_scheduled_events(new_data['guild_scheduled_events']) if new_data['guild_scheduled_events']
process_stage_instances(new_data['stage_instances']) if new_data['stage_instances']
end

# Adds a channel to this server's cache
Expand Down Expand Up @@ -1523,6 +1524,15 @@ def process_scheduled_events(events)
@scheduled_events[event.resolve_id] = event
end
end

def process_stage_instances(instances)
return unless instances

instances.each do |element|
channel = @channels_by_id[element['channel_id'].to_i]
channel&.process_stage_instance(StageInstance.new(element, channel, @bot))
end
end
end

# A ban entry on a server.
Expand Down
66 changes: 66 additions & 0 deletions lib/discordrb/data/stage_instance.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# frozen_string_literal: true

module Discordrb
# Metadata about a live stage.
class StageInstance
include IDObject

# @return [String] the topic of the stage instance.
attr_reader :topic

# @return [Channel] the stage channel of the stage instance.
attr_reader :channel

# @return [Integer, nil] the ID of the scheduled event for the stage instance.
attr_reader :scheduled_event_id

# @!visibility private
def initialize(data, channel, bot)
@bot = bot
@channel = channel
@id = data['id'].to_i
update_data(data)
end

# Get the stage instance's server.
# @return [Server] The server of the stage instance.
def server
@channel.server
end

# Modify the properties of the stage instance.
# @param topic [String] The new 1-120 character topic of the stage instance.
# @param reason [String, nil] The reason to show in the audit log for updating the stage instance.
# @return [nil]
def modify(topic: :undef, reason: nil)
update_data(JSON.parse(API::Channel.update_stage_instance(@bot.token, @channel.id, topic:, reason:)))
nil
end

# Get the scheduled event associated with the stage instance.
# @return [ScheduledEvent, nil] The scheduled event associated with the stage instance, or `nil`.
def scheduled_event
server.scheduled_event(@scheduled_event_id) if @scheduled_event_id
end

# Permanenty delete the stage instance; this cannot be undone.
# @param reason [String, nil] The reason to show in the audit log for deleting the stage instance.
# @return [nil]
def delete(reason: nil)
API::Channel.delete_stage_instance(@bot.token, @channel.id, reason: reason)
server.delete_stage_instance(@id)
nil
end

# @!visibility private
def update_data(new_data)
@topic = new_data['topic']
@scheduled_event_id = new_data['guild_scheduled_event_id']&.to_i
end

# @!visibility private
def inspect
"<StageInstance id=#{@id} topic=\"#{@topic}\" scheduled_event_id=#{@scheduled_event_id}>"
end
end
end
75 changes: 75 additions & 0 deletions lib/discordrb/events/stage_instances.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# frozen_string_literal: true

module Discordrb::Events
# Generic superclass for stage instance events.
class StageInstanceEvent < Event
# @return [Channel] the channel of the stage instance.
attr_reader :channel

# @return [StageInstance] the stage instance in question.
attr_reader :stage_instance

# @!visibility private
def initialize(data, bot)
@bot = bot
@channel = bot.channel(data['channel_id'].to_i)
@stage_instance = @channel.stage_instance(request: true)
end
end

# Raised whenever a stage instance is created.
class StageInstanceCreateEvent < StageInstanceEvent; end

# Raised whenever a stage instance is updated.
class StageInstanceUpdateEvent < StageInstanceEvent; end

# Raised whenever a stage instance is deleted.
class StageInstanceDeleteEvent < StageInstanceEvent
# @!visibility private
def initialize(data, bot)
@bot = bot
@channel = bot.channel(data['channel_id'].to_i)
@stage_instance = Discordrb::StageInstance.new(data, channel, @bot)
end
end

# Generic event handler class for stage instance events.
class StageInstanceEventHandler < EventHandler
# @!visibility private
def matches?(event)
return false unless event.is_a?(StageInstanceEvent)

[
matches_all(@attributes[:topic], event.stage_instance.topic) do |a, e|
case a
when Regexp
a.match?(e)
else
a == e
end
end,

matches_all(@attributes[:channel], event.stage_instance.channel) do |a, e|
a&.resolve_id == e&.resolve_id
end,

matches_all(@attributes[:server], event.stage_instance.channel.server) do |a, e|
a&.resolve_id == e&.resolve_id
end,

matches_all(@attributes[:scheduled_event], event.stage_instance.scheduled_event_id) do |a, e|
a&.resolve_id == e&.resolve_id
end
].reduce(true, &:&)
end
end

# Event handler for :STAGE_INSTANCE_CREATE events.
class StageInstanceCreateEventHandler < StageInstanceEventHandler; end

# Event handler for :STAGE_INSTANCE_UPDATE events.
class StageInstanceUpdateEventHandler < StageInstanceEventHandler; end

# Event handler for :STAGE_INSTANCE_DELETE events.
class StageInstanceDeleteEventHandler < StageInstanceEventHandler; end
end
Loading