Forum channels (#1246)

* feat: forums

Initial implementation of forum channels REST API

* fix: linter

* feat: cosmetic changes

Added periods in the end of all documentation comments

* fix(ChannelFlagRequireTag): incorrect value

Fix incorrect value of ChannelFlagRequireTag.

* refactor(DefaultReaction): rename to ForumDefaultReaction

Rename DefaultReaction to ForumDefaultReaction.

* fix(Channel): use ForumDefaultReaction

Use ForumDefaultReaction instead of DefaultReaction in DefaultReactionEmoji field.

* fix: gofmt

* feat: cosmetic changes

* Change "GUILD_FORUM" to "forum" in comment to ForumTag
* Fix spacing for documentation of embeds parameter in ForumThreadStartEmbeds

* docs(ForumThreadStartComplex): align parameters

Align documentation for parameters of ForumThreadStartComplex.
This commit is contained in:
Fedor Lapshin 2022-09-29 23:24:27 +03:00 committed by GitHub
parent fea3d77574
commit e57064892a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 176 additions and 22 deletions

View file

@ -2496,6 +2496,100 @@ func (s *Session) ThreadStart(channelID, name string, typ ChannelType, archiveDu
})
}
// ForumThreadStartComplex starts a new thread (creates a post) in a forum channel.
// channelID : Channel to create thread in.
// threadData : Parameters of the thread.
// messageData : Parameters of the starting message.
func (s *Session) ForumThreadStartComplex(channelID string, threadData *ThreadStart, messageData *MessageSend) (th *Channel, err error) {
endpoint := EndpointChannelThreads(channelID)
// TODO: Remove this when compatibility is not required.
if messageData.Embed != nil {
if messageData.Embeds == nil {
messageData.Embeds = []*MessageEmbed{messageData.Embed}
} else {
err = fmt.Errorf("cannot specify both Embed and Embeds")
return
}
}
for _, embed := range messageData.Embeds {
if embed.Type == "" {
embed.Type = "rich"
}
}
// TODO: Remove this when compatibility is not required.
files := messageData.Files
if messageData.File != nil {
if files == nil {
files = []*File{messageData.File}
} else {
err = fmt.Errorf("cannot specify both File and Files")
return
}
}
data := struct {
*ThreadStart
Message *MessageSend `json:"message"`
}{ThreadStart: threadData, Message: messageData}
var response []byte
if len(files) > 0 {
contentType, body, encodeErr := MultipartBodyWithJSON(data, files)
if encodeErr != nil {
return th, encodeErr
}
response, err = s.request("POST", endpoint, contentType, body, endpoint, 0)
} else {
response, err = s.RequestWithBucketID("POST", endpoint, data, endpoint)
}
if err != nil {
return
}
err = unmarshal(response, &th)
return
}
// ForumThreadStart starts a new thread (post) in a forum channel.
// channelID : Channel to create thread in.
// name : Name of the thread.
// archiveDuration : Auto archive duration.
// content : Content of the starting message.
func (s *Session) ForumThreadStart(channelID, name string, archiveDuration int, content string) (th *Channel, err error) {
return s.ForumThreadStartComplex(channelID, &ThreadStart{
Name: name,
AutoArchiveDuration: archiveDuration,
}, &MessageSend{Content: content})
}
// ForumThreadStartEmbed starts a new thread (post) in a forum channel.
// channelID : Channel to create thread in.
// name : Name of the thread.
// archiveDuration : Auto archive duration.
// embed : Embed data of the starting message.
func (s *Session) ForumThreadStartEmbed(channelID, name string, archiveDuration int, embed *MessageEmbed) (th *Channel, err error) {
return s.ForumThreadStartComplex(channelID, &ThreadStart{
Name: name,
AutoArchiveDuration: archiveDuration,
}, &MessageSend{Embeds: []*MessageEmbed{embed}})
}
// ForumThreadStartEmbeds starts a new thread (post) in a forum channel.
// channelID : Channel to create thread in.
// name : Name of the thread.
// archiveDuration : Auto archive duration.
// embeds : Embeds data of the starting message.
func (s *Session) ForumThreadStartEmbeds(channelID, name string, archiveDuration int, embeds []*MessageEmbed) (th *Channel, err error) {
return s.ForumThreadStartComplex(channelID, &ThreadStart{
Name: name,
AutoArchiveDuration: archiveDuration,
}, &MessageSend{Embeds: embeds})
}
// ThreadJoin adds current user to a thread
func (s *Session) ThreadJoin(id string) error {
endpoint := EndpointThreadMember(id, "@me")

View file

@ -254,6 +254,20 @@ const (
ChannelTypeGuildPublicThread ChannelType = 11
ChannelTypeGuildPrivateThread ChannelType = 12
ChannelTypeGuildStageVoice ChannelType = 13
ChannelTypeGuildForum ChannelType = 15
)
// ChannelFlags represent flags of a channel/thread.
type ChannelFlags int
// Block containing known ChannelFlags values.
const (
// ChannelFlagPinned indicates whether the thread is pinned in the forum channel.
// NOTE: forum threads only.
ChannelFlagPinned ChannelFlags = 1 << 1
// ChannelFlagRequireTag indicates whether a tag is required to be specified when creating a thread.
// NOTE: forum channels only.
ChannelFlagRequireTag ChannelFlags = 1 << 4
)
// A Channel holds all data related to an individual Discord channel.
@ -332,6 +346,18 @@ type Channel struct {
// All thread members. State channels only.
Members []*ThreadMember `json:"-"`
// Channel flags.
Flags ChannelFlags `json:"flags"`
// The set of tags that can be used in a forum channel.
AvailableTags []ForumTag `json:"available_tags"`
// The IDs of the set of tags that have been applied to a thread in a forum channel.
AppliedTags []string `json:"applied_tags"`
// Emoji to use as the default reaction to a forum post.
DefaultReactionEmoji ForumDefaultReaction `json:"default_reaction_emoji"`
}
// Mention returns a string which mentions the channel
@ -355,6 +381,7 @@ type ChannelEdit struct {
PermissionOverwrites []*PermissionOverwrite `json:"permission_overwrites,omitempty"`
ParentID string `json:"parent_id,omitempty"`
RateLimitPerUser *int `json:"rate_limit_per_user,omitempty"`
Flags *ChannelFlags `json:"flags,omitempty"`
// NOTE: threads only
@ -362,6 +389,14 @@ type ChannelEdit struct {
AutoArchiveDuration int `json:"auto_archive_duration,omitempty"`
Locked *bool `json:"locked,omitempty"`
Invitable *bool `json:"invitable,omitempty"`
// NOTE: forum channels only
AvailableTags *[]ForumTag `json:"available_tags,omitempty"`
DefaultReactionEmoji *ForumDefaultReaction `json:"default_reaction_emoji,omitempty"`
// NOTE: forum threads only
AppliedTags *[]string `json:"applied_tags,omitempty"`
}
// A ChannelFollow holds data returned after following a news channel
@ -395,6 +430,9 @@ type ThreadStart struct {
Type ChannelType `json:"type,omitempty"`
Invitable bool `json:"invitable"`
RateLimitPerUser int `json:"rate_limit_per_user,omitempty"`
// NOTE: forum threads only
AppliedTags []string `json:"applied_tags,omitempty"`
}
// ThreadMetadata contains a number of thread-specific channel fields that are not needed by other channel types.
@ -438,6 +476,24 @@ type AddedThreadMember struct {
Presence *Presence `json:"presence"`
}
// ForumDefaultReaction specifies emoji to use as the default reaction to a forum post.
// NOTE: Exactly one of EmojiID and EmojiName must be set.
type ForumDefaultReaction struct {
// The id of a guild's custom emoji.
EmojiID string `json:"emoji_id,omitempty"`
// The unicode character of the emoji.
EmojiName string `json:"emoji_name,omitempty"`
}
// ForumTag represents a tag that is able to be applied to a thread in a forum channel.
type ForumTag struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
Moderated bool `json:"moderated"`
EmojiID string `json:"emoji_id,omitempty"`
EmojiName string `json:"emoji_name,omitempty"`
}
// Emoji struct holds data related to Emoji's
type Emoji struct {
ID string `json:"id"`
@ -2074,6 +2130,7 @@ const (
ErrCodeUnknownGuildWelcomeScreen = 10069
ErrCodeUnknownGuildScheduledEvent = 10070
ErrCodeUnknownGuildScheduledEventUser = 10071
ErrUnknownTag = 10087
ErrCodeBotsCannotUseEndpoint = 20001
ErrCodeOnlyBotsCanUseEndpoint = 20002
@ -2087,28 +2144,30 @@ const (
ErrCodeStageTopicContainsNotAllowedWordsForPublicStages = 20031
ErrCodeGuildPremiumSubscriptionLevelTooLow = 20035
ErrCodeMaximumGuildsReached = 30001
ErrCodeMaximumPinsReached = 30003
ErrCodeMaximumNumberOfRecipientsReached = 30004
ErrCodeMaximumGuildRolesReached = 30005
ErrCodeMaximumNumberOfWebhooksReached = 30007
ErrCodeMaximumNumberOfEmojisReached = 30008
ErrCodeTooManyReactions = 30010
ErrCodeMaximumNumberOfGuildChannelsReached = 30013
ErrCodeMaximumNumberOfAttachmentsInAMessageReached = 30015
ErrCodeMaximumNumberOfInvitesReached = 30016
ErrCodeMaximumNumberOfAnimatedEmojisReached = 30018
ErrCodeMaximumNumberOfServerMembersReached = 30019
ErrCodeMaximumNumberOfGuildDiscoverySubcategoriesReached = 30030
ErrCodeGuildAlreadyHasATemplate = 30031
ErrCodeMaximumNumberOfThreadParticipantsReached = 30033
ErrCodeMaximumNumberOfBansForNonGuildMembersHaveBeenExceeded = 30035
ErrCodeMaximumNumberOfBansFetchesHasBeenReached = 30037
ErrCodeMaximumNumberOfUncompletedGuildScheduledEventsReached = 30038
ErrCodeMaximumNumberOfStickersReached = 30039
ErrCodeMaximumNumberOfPruneRequestsHasBeenReached = 30040
ErrCodeMaximumNumberOfGuildWidgetSettingsUpdatesHasBeenReached = 30042
ErrCodeMaximumNumberOfEditsToMessagesOlderThanOneHourReached = 30046
ErrCodeMaximumGuildsReached = 30001
ErrCodeMaximumPinsReached = 30003
ErrCodeMaximumNumberOfRecipientsReached = 30004
ErrCodeMaximumGuildRolesReached = 30005
ErrCodeMaximumNumberOfWebhooksReached = 30007
ErrCodeMaximumNumberOfEmojisReached = 30008
ErrCodeTooManyReactions = 30010
ErrCodeMaximumNumberOfGuildChannelsReached = 30013
ErrCodeMaximumNumberOfAttachmentsInAMessageReached = 30015
ErrCodeMaximumNumberOfInvitesReached = 30016
ErrCodeMaximumNumberOfAnimatedEmojisReached = 30018
ErrCodeMaximumNumberOfServerMembersReached = 30019
ErrCodeMaximumNumberOfGuildDiscoverySubcategoriesReached = 30030
ErrCodeGuildAlreadyHasATemplate = 30031
ErrCodeMaximumNumberOfThreadParticipantsReached = 30033
ErrCodeMaximumNumberOfBansForNonGuildMembersHaveBeenExceeded = 30035
ErrCodeMaximumNumberOfBansFetchesHasBeenReached = 30037
ErrCodeMaximumNumberOfUncompletedGuildScheduledEventsReached = 30038
ErrCodeMaximumNumberOfStickersReached = 30039
ErrCodeMaximumNumberOfPruneRequestsHasBeenReached = 30040
ErrCodeMaximumNumberOfGuildWidgetSettingsUpdatesHasBeenReached = 30042
ErrCodeMaximumNumberOfEditsToMessagesOlderThanOneHourReached = 30046
ErrCodeMaximumNumberOfPinnedThreadsInForumChannelHasBeenReached = 30047
ErrCodeMaximumNumberOfTagsInForumChannelHasBeenReached = 30048
ErrCodeUnauthorized = 40001
ErrCodeActionRequiredVerifiedAccount = 40002
@ -2121,6 +2180,7 @@ const (
ErrCodeMessageAlreadyCrossposted = 40033
ErrCodeAnApplicationWithThatNameAlreadyExists = 40041
ErrCodeInteractionHasAlreadyBeenAcknowledged = 40060
ErrCodeTagNamesMustBeUnique = 40061
ErrCodeMissingAccess = 50001
ErrCodeInvalidAccountType = 50002