Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Forum channels #1246

Merged
merged 9 commits into from
Sep 29, 2022
Merged
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
94 changes: 94 additions & 0 deletions restapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
104 changes: 82 additions & 22 deletions structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -355,13 +381,22 @@ 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

Archived *bool `json:"archived,omitempty"`
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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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"`
Expand Down Expand Up @@ -2074,6 +2130,7 @@ const (
ErrCodeUnknownGuildWelcomeScreen = 10069
ErrCodeUnknownGuildScheduledEvent = 10070
ErrCodeUnknownGuildScheduledEventUser = 10071
ErrUnknownTag = 10087

ErrCodeBotsCannotUseEndpoint = 20001
ErrCodeOnlyBotsCanUseEndpoint = 20002
Expand All @@ -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
Expand All @@ -2121,6 +2180,7 @@ const (
ErrCodeMessageAlreadyCrossposted = 40033
ErrCodeAnApplicationWithThatNameAlreadyExists = 40041
ErrCodeInteractionHasAlreadyBeenAcknowledged = 40060
ErrCodeTagNamesMustBeUnique = 40061

ErrCodeMissingAccess = 50001
ErrCodeInvalidAccountType = 50002
Expand Down