lib/discorb/channel/forum.rb
# frozen_string_literal: true
module Discorb
#
# Represents a forum channel.
#
class ForumChannel < GuildChannel
# @return [String] The guideline of the channel.
attr_reader :guideline
alias topic guideline
# @return [Boolean] Whether the channel is nsfw.
attr_reader :nsfw
# @return [Integer] The rate limit to create thread per user (Slowmode) in the channel.
attr_reader :rate_limit_per_user
alias slowmode rate_limit_per_user
alias thread_slowmode rate_limit_per_user
# @return [Integer] The rate limit to send message per user (Slowmode) in the channel.
attr_reader :message_rate_limit_per_user
alias message_slowmode message_rate_limit_per_user
# @return [Integer] The default value of duration of auto archive.
attr_reader :default_auto_archive_duration
# @return [:latest_activity, :creation_date] The default sort order of the channel.
attr_reader :default_sort_order
# @return [Array<Discorb::ForumChannel::Tag>] The tags in the channel.
attr_reader :tags
# @return [Boolean] Whether at least one tag is required.
attr_reader :require_tag
alias require_tag? require_tag
@channel_type = 15
# @!attribute [r] threads
# @return [Array<Discorb::ThreadChannel>] The threads in the channel.
def threads
guild.threads.select { |thread| thread.parent == self }
end
DEFAULT_SORT_ORDER = { 1 => :latest_activity, 2 => :creation_date }.freeze
#
# Represents a tag in the forum channel.
#
class Tag < DiscordModel
# @return [Snowflake] The id of the tag.
attr_reader :id
# @return [String] The name of the tag.
attr_reader :name
# @return [Boolean] Whether the tag is moderated. (aka moderator only)
attr_reader :moderated
alias moderated? moderated
# @return [Discorb::Emoji] The emoji of the tag.
attr_reader :emoji
#
# Initializes a new tag.
#
# @param [String] name The name of the tag.
# @param [Discorb::Emoji] emoji The emoji of the tag.
# @param [Boolean] moderated Whether the tag is moderated. (aka moderator only)
#
# @return [Discorb::ForumChannel::Tag] The new tag.
#
def initialize(name, emoji, moderated: false)
@id = nil
@name = name
@emoji = emoji
@moderated = moderated
end
#
# Returns the tag as a hash.
#
# @return [Hash] The tag as a hash.
#
def to_hash
{ id:, name:, moderated:, emoji: }
end
# @private
def self.from_data(guild, data)
tag = allocate
tag.send(:_set_data, guild, data)
tag
end
def inspect
"#<#{self.class}: #{name} id=#{id}>"
end
private
def _set_data(guild, data)
@id = Snowflake.new(data[:id])
@name = data[:name]
@moderated = data[:moderated]
@emoji =
if data[:emoji_id]
guild.emojis[data[:emoji_id]]
elsif data[:emoji_name]
UnicodeEmoji.new(data[:emoji_name])
end
end
end
#
# Represents a thread in the forum channel.
#
class Post < ThreadChannel
@channel_type = 11
# @return [Array<Discorb::ForumChannel::Tag>] The tags of the post.
attr_reader :tags
# @return [Boolean] Whether the post is pinned.
attr_reader :pinned
alias pinned? pinned
#
# Edit the thread.
# @async
# @macro edit
#
# @param [String] name The name of the thread.
# @param [Boolean] archived Whether the thread is archived or not.
# @param [Integer] auto_archive_duration The auto archive duration in seconds.
# @param [Integer] archive_in Alias of `auto_archive_duration`.
# @param [Boolean] locked Whether the thread is locked or not.
# @param [String] reason The reason of editing the thread.
# @param [Boolean] pinned Whether the thread is pinned or not.
# @param [Array<Discorb::ForumChannel::Tag>] tags The tags of the thread.
#
# @return [Async::Task<self>] The edited thread.
#
# @see #archive
# @see #lock
# @see #unarchive
# @see #unlock
# @see #pin
# @see #unpin
# @see #add_tags
# @see #remove_tags
#
def edit(
name: Discorb::Unset,
archived: Discorb::Unset,
auto_archive_duration: Discorb::Unset,
archive_in: Discorb::Unset,
locked: Discorb::Unset,
pinned: Discorb::Unset,
tags: Discorb::Unset,
reason: nil
)
Async do
payload = {}
payload[:name] = name if name != Discorb::Unset
payload[:archived] = archived if archived != Discorb::Unset
auto_archive_duration ||= archive_in
payload[
:auto_archive_duration
] = auto_archive_duration if auto_archive_duration != Discorb::Unset
payload[:locked] = locked if locked != Discorb::Unset
payload[:flags] = pinned ? 1 : 0 if pinned != Discorb::Unset
payload[:applied_tags] = tags.map(&:id) if tags != Discorb::Unset
@client
.http
.request(
Route.new("/channels/#{@id}", "//channels/:channel_id", :patch),
payload,
audit_log_reason: reason
)
.wait
self
end
end
#
# Pins the thread.
# @async
#
# @param [String] reason The reason of pinning the thread.
#
# @return [Async::Task<self>] The pinned thread.
#
# @see #unpin
# @see #pinned?
#
def pin(reason: nil)
edit(pinned: true, reason:)
end
#
# Unpins the thread.
# @async
#
# @param [String] reason The reason of unpinning the thread.
#
# @return [Async::Task<self>] The unpinned thread.
#
# @see #pin
# @see #pinned?
#
def unpin(reason: nil)
edit(pinned: false, reason:)
end
#
# Adds tags to the thread.
# @async
#
# @param [Array<Discorb::ForumChannel::Tag>] tags The tags to add.
# @param [String] reason The reason of adding tags.
# @return [Async::Task<self>] The thread with added tags.
#
# @see #remove_tags
# @see #tags
#
def add_tags(*tags, reason: nil)
edit(tags: self.tags + tags, reason:)
end
#
# Removes tags from the thread.
# @async
#
# @param [Array<Discorb::ForumChannel::Tag>] tags The tags to remove.
# @param [String] reason The reason of removing tags.
# @return [Async::Task<self>] The thread with removed tags.
#
# @see #add_tags
# @see #tags
#
def remove_tags(*tags, reason: nil)
edit(tags: self.tags - tags, reason:)
end
private
def _set_data(data)
super(data)
@tags = data[:applied_tags].map { |tag| parent.tags[tag] }
@pinned = data[:flags] == 1
end
end
def initialize(client, data, no_cache: false)
super
_set_data(data)
end
def inspect
"#<#{self.class}: #{name} id=#{id}>"
end
#
# Edits the channel.
# @async
# @macro edit
#
# @param [String] name The name of the channel.
# @param [Integer] position The position of the channel.
# @param [Discorb::CategoryChannel, nil] category The parent of channel. Specify `nil` to remove the parent.
# @param [Discorb::CategoryChannel, nil] parent Alias of `category`.
# @param [String] topic The topic of the channel.
# @param [String] guideline Alias of `topic`.
# @param [Boolean] nsfw Whether the channel is nsfw.
# @param [Integer] rate_limit_per_user The rate limit per user (Slowmode) in the channel.
# @param [Integer] slowmode Alias of `rate_limit_per_user`.
# @param [Integer] thread_rate_limit_per_user The rate limit per user (Slowmode) in threads.
# @param [Integer] thread_slowmode Alias of `thread_rate_limit_per_user`.
# @param [Integer] default_auto_archive_duration The default auto archive duration of the channel.
# @param [Integer] archive_in Alias of `default_auto_archive_duration`.
# @param [Boolean] require_tag Whether the channel requires tag to create thread.
# @param [Discorb::Emoji] default_reaction_emoji The default reaction emoji of the channel.
# @param [:latest_activity, :creation_date] default_sort_order The default sort order of the channel.
# @param [Array<Discorb::ForumChannel::Tag>] tags The tags of the channel.
# @param [String] reason The reason of editing the channel.
#
# @return [Async::Task<self>] The edited channel.
#
# @see #create_tags
# @see #delete_tags
#
def edit(
name: Discorb::Unset,
position: Discorb::Unset,
category: Discorb::Unset,
parent: Discorb::Unset,
topic: Discorb::Unset,
guideline: Discorb::Unset,
nsfw: Discorb::Unset,
rate_limit_per_user: Discorb::Unset,
slowmode: Discorb::Unset,
thread_rate_limit_per_user: Discorb::Unset,
thread_slowmode: Discorb::Unset,
default_auto_archive_duration: Discorb::Unset,
archive_in: Discorb::Unset,
require_tag: Discorb::Unset,
default_reaction_emoji: Discorb::Unset,
default_sort_order: Discorb::Unset,
tags: Discorb::Unset,
reason: nil
)
Async do
payload = {}
payload[:name] = name unless name == Discorb::Unset
payload[:position] = position unless position == Discorb::Unset
payload[:parent_id] = category.id unless category == Discorb::Unset
payload[:parent_id] = parent.id unless parent == Discorb::Unset
payload[:topic] = topic unless topic == Discorb::Unset
payload[:topic] = guideline unless guideline == Discorb::Unset
payload[:nsfw] = nsfw unless nsfw == Discorb::Unset
payload[
:rate_limit_per_user
] = rate_limit_per_user unless rate_limit_per_user == Discorb::Unset
payload[:rate_limit_per_user] = slowmode unless slowmode ==
Discorb::Unset
payload[
:thread_rate_limit_per_user
] = thread_rate_limit_per_user unless thread_rate_limit_per_user ==
Discorb::Unset
payload[
:thread_rate_limit_per_user
] = thread_slowmode unless thread_slowmode == Discorb::Unset
payload[
:default_auto_archive_duration
] = default_auto_archive_duration unless default_auto_archive_duration ==
Discorb::Unset
payload[
:default_auto_archive_duration
] = archive_in unless archive_in == Discorb::Unset
payload[:flags] = (require_tag ? 1 << 4 : 0) unless require_tag ==
Discorb::Unset
payload[:default_reaction_emoji] = default_reaction_emoji
.to_hash
.then do |e|
{ emoji_name: e[:name], emoji_id: e[:id] }
end unless default_reaction_emoji == Discorb::Unset
payload[:default_sort_order] = DEFAULT_SORT_ORDER.key(
default_sort_order
) unless default_sort_order == Discorb::Unset
payload[:available_tags] = tags.map(&:to_hash) unless tags ==
Discorb::Unset
ret =
@client
.http
.request(
Route.new("/channels/#{id}", "//channels/:channel_id", :patch),
reason:
)
.wait
_set_data(ret)
if tags != Discorb::Unset
tags
.reject(&:id)
.each do |tag|
tag.instance_variable_set(
:@id,
ret[:available_tags].find { |t| t.to_hash == tag.to_hash }[:id]
)
end
end
self
end
end
alias modify edit
#
# Creates tags in the channel.
# @async
#
# @param [Array<Discorb::ForumChannel::Tag>] tags The tags to create.
# @param [String] reason The reason of creating tags.
#
# @return [Async::Task<self>] The edited channel.
#
# @see #edit
#
def create_tags(*tags, reason: nil)
edit(tags: self.tags + tags, reason:)
end
#
# Deletes tags from the channel.
# @async
#
# @param [Array<Discorb::ForumChannel::Tag>] tags The tags to delete.
# @param [String] reason The reason of deleting tags.
#
# @return [Async::Task<self>] The edited channel.
#
# @see #edit
#
def delete_tags(*tags, reason: nil)
edit(tags: self.tags - tags, reason:)
end
#
# Create webhook in the channel.
# @async
#
# @param [String] name The name of the webhook.
# @param [Discorb::Image] avatar The avatar of the webhook.
#
# @return [Async::Task<Discorb::Webhook::IncomingWebhook>] The created webhook.
#
def create_webhook(name, avatar: nil)
Async do
payload = {}
payload[:name] = name
payload[:avatar] = avatar.to_s if avatar
_resp, data =
@client
.http
.request(
Route.new(
"/channels/#{@id}/webhooks",
"//channels/:channel_id/webhooks",
:post
),
payload
)
.wait
Webhook.from_data(@client, data)
end
end
#
# Fetch webhooks in the channel.
# @async
#
# @return [Async::Task<Array<Discorb::Webhook>>] The webhooks in the channel.
#
def fetch_webhooks
Async do
_resp, data =
@client
.http
.request(
Route.new(
"/channels/#{@id}/webhooks",
"//channels/:channel_id/webhooks",
:get
)
)
.wait
data.map { |webhook| Webhook.from_data(@client, webhook) }
end
end
#
# Create post in the channel.
# @async
#
# @param [Title] title The title of the thread.
# @param [String] content The message content.
# @param [Discorb::Embed] embed The embed to send.
# @param [Array<Discorb::Embed>] embeds The embeds to send.
# @param [Discorb::AllowedMentions] allowed_mentions The allowed mentions.
# @param [Array<Discorb::Component>, Array<Array<Discorb::Component>>] components The components to send.
# @param [Discorb::Attachment] attachment The attachment to send.
# @param [Array<Discorb::Attachment>] attachments The attachments to send.
# @param [Array<Discorb::ForumChannel::Tag>] tags The tags to attach.
# @param [:hour, :day, :three_days, :week] auto_archive_duration The duration of auto-archiving.
# @param [Integer] rate_limit_per_user The rate limit per user.
# @param [Integer] slowmode Alias of `rate_limit_per_user`.
#
# @return [Async::Task<Discorb::ForumChannel::Post>] The created thread.
#
def create_post(
title,
content = nil,
embed: nil,
embeds: nil,
allowed_mentions: nil,
components: nil,
attachment: nil,
attachments: nil,
tags: nil,
auto_archive_duration: nil,
rate_limit_per_user: nil,
slowmode: nil,
reason: nil
)
Async do
message_payload = {}
message_payload[:content] = content if content
tmp_embed =
if embed
[embed]
elsif embeds
embeds
end
message_payload[:embeds] = tmp_embed.map(&:to_hash) if tmp_embed
message_payload[:allowed_mentions] = (
if allowed_mentions
allowed_mentions.to_hash(@client.allowed_mentions)
else
@client.allowed_mentions.to_hash
end
)
message_payload[:components] = Component.to_payload(
components
) if components
attachments ||= attachment ? [attachment] : []
message_payload[:attachments] = attachments.map.with_index do |a, i|
{ id: i, filename: a.filename, description: a.description }
end
payload = {}
payload[:name] = title
payload[
:auto_archive_duration
] = auto_archive_duration if auto_archive_duration
payload[:rate_limit_per_user] = rate_limit_per_user ||
slowmode if rate_limit_per_user || slowmode
payload[:message] = message_payload
payload[:applied_tags] = tags.map(&:id) if tags
_resp, data =
@client
.http
.multipart_request(
Route.new(
"/channels/#{channel_id.wait}/threads",
"//channels/:channel_id/threads",
:post
),
payload,
attachments,
audit_log_reason: reason
)
.wait
Channel.make_channel(@client, data)
end
end
alias start_thread create_post
alias create_thread start_thread
#
# Fetch archived threads in the channel.
# @async
#
# @return [Async::Task<Array<Discorb::ThreadChannel>>] The archived threads in the channel.
#
def fetch_archived_threads
Async do
_resp, data =
@client
.http
.request(
Route.new(
"/channels/#{@id}/threads/archived/public",
"//channels/:channel_id/threads/archived/public",
:get
)
)
.wait
data.map { |thread| Channel.make_channel(@client, thread) }
end
end
private
def _set_data(data)
@topic = data[:topic]
@nsfw = data[:nsfw]
@rate_limit_per_user = data[:rate_limit_per_user]
@default_auto_archive_duration = data[:default_auto_archive_duration]
@message_rate_limit_per_user = data[:message_rate_limit_per_user]
@default_sort_order = DEFAULT_SORT_ORDER[data[:default_sort_order]]
@tags =
Dictionary.new(
data[:available_tags].to_h do |tag|
Tag.from_data(guild, tag).then { |t| [t.id, t] }
end
)
@require_tag = data[:flags] & (1 << 4) != 0
super
end
end
end