feat: Guild Forums (#113) which closes #25

* feat: Guild Forums

* fix
This commit is contained in:
Marcos Susaña 2022-09-15 02:11:03 -04:00 committed by GitHub
parent dde0bffc57
commit 659f4512a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 154 additions and 5 deletions

View File

@ -175,6 +175,20 @@ export interface BaseRole {
unicodeEmoji?: string;
}
/** https://discord.com/developers/docs/resources/channel#forum-tag-object */
export interface DiscordForumTag {
/** the id of the tag */
id: Snowflake;
/** the name of the tag (0-20 characters) */
name: string;
/** whether this tag can only be added to or removed from threads by a member with the MANAGE_THREADS permission */
moderated: boolean;
/** the id of a guild's custom emoji * */
emoji_id: Snowflake | null;
/** he unicode character of the emoji */
emoji_name: string | null;
}
/** https://discord.com/developers/docs/resources/guild#guild-object-guild-features */
export enum GuildFeatures {
/** Guild has access to set an invite splash background */

View File

@ -12,6 +12,7 @@ import type {
DefaultMessageNotificationLevels,
EmbedTypes,
ExplicitContentFilterLevels,
DiscordForumTag,
GatewayEventNames,
GuildFeatures,
GuildNsfwLevel,
@ -754,6 +755,17 @@ export interface DiscordChannel {
newly_created?: boolean;
/** The recipients of the DM*/
recipients?: DiscordUser[];
/** number of messages ever sent in a thread */
total_message_sent?: number;
/** the set of tags that can be used in a GUILD_FORUM channel */
available_tags?: DiscordForumTag[];
/** the IDs of the set of tags that have been applied to a thread in a GUILD_FORUM channel */
applied_tags?: string[];
/** the emoji to show in the add reaction button on a thread in a GUILD_FORUM channel */
default_reaction_emoji?: { emoji_id: string; emoji_name: string | null };
/** the initial rate_limit_per_user to set on newly created threads in a channel. this field is copied to the thread at creation time and does not live update. */
default_thread_rate_limit_per_user?: number;
}
/** https://discord.com/developers/docs/topics/gateway#presence-update */
@ -1051,6 +1063,8 @@ export interface DiscordMessage {
components?: DiscordMessageComponents;
/** Sent if the message contains stickers */
sticker_items?: DiscordStickerItem[];
/** A generally increasing integer (there may be gaps or duplicates) */
position?: number;
}
/** https://discord.com/developers/docs/resources/channel#channel-mention-object */

View File

@ -104,6 +104,11 @@ export abstract class BaseChannel implements Model {
return this.type === ChannelTypes.GuildStageVoice;
}
/** If the channel is a ForumChannel */
isForum(): this is ForumChannel {
return this.type === ChannelTypes.GuildForum;
}
async fetch(channelId?: Snowflake): Promise<Channel> {
const channel = await this.session.rest.get<DiscordChannel>(CHANNEL(channelId ?? this.id));
@ -506,10 +511,20 @@ export interface EditNewsChannelOptions extends EditGuildChannelOptions {
defaultAutoArchiveDuration?: number | null;
}
export interface EditForumChannelOptions extends EditGuildChannelOptions {
availableTags?: ForumTag[];
defaultReactionEmoji?: DefaultReactionEmoji;
defaultThreadRateLimitPerUser?: number;
}
export interface EditGuildTextChannelOptions extends EditNewsChannelOptions {
rateLimitPerUser?: number | null;
}
export interface EditThreadChannelOptions extends EditGuildTextChannelOptions {
appliedTags: string[];
}
export interface EditStageChannelOptions extends EditGuildChannelOptions {
bitrate?: number | null;
rtcRegion?: Snowflake | null;
@ -589,9 +604,16 @@ export class GuildChannel extends BaseChannel implements Model {
async edit(options: EditNewsChannelOptions): Promise<NewsChannel>;
async edit(options: EditStageChannelOptions): Promise<StageChannel>;
async edit(options: EditVoiceChannelOptions): Promise<VoiceChannel>;
async edit(options: EditForumChannelOptions): Promise<ForumChannel>;
async edit(options: EditThreadChannelOptions): Promise<ThreadChannel>;
async edit(
options: EditGuildTextChannelOptions | EditNewsChannelOptions | EditVoiceChannelOptions,
): Promise<Channel> {
options:
| EditGuildTextChannelOptions
| EditNewsChannelOptions
| EditVoiceChannelOptions
| EditForumChannelOptions
| EditThreadChannelOptions
): Promise<GuildChannel> {
const channel = await this.session.rest.patch<DiscordChannel>(
CHANNEL(this.id),
{
@ -607,12 +629,30 @@ export class GuildChannel extends BaseChannel implements Model {
parent_id: 'parentId' in options ? options.parentId : undefined,
rtc_region: 'rtcRegion' in options ? options.rtcRegion : undefined,
video_quality_mode: 'videoQualityMode' in options ? options.videoQualityMode : undefined,
applied_tags: 'appliedTags' in options ? options.appliedTags : undefined,
default_auto_archive_duration: 'defaultAutoArchiveDuration' in options
? options.defaultAutoArchiveDuration
: undefined,
default_reaction_emoji: 'defaultReactionEmoji' in options
? options.defaultReactionEmoji
: undefined,
default_thread_rate_limit_per_user: 'defaultThreadRateLimitPerUser' in options
? options.defaultThreadRateLimitPerUser
: undefined,
available_tags: 'availableTags' in options
? options.availableTags?.map(at => {
return {
id: at.id,
name: at.name,
moderated: at.moderated,
emoji_id: at.emojiId,
emoji_name: at.emojiName
};
})
: undefined
},
);
return ChannelFactory.from(this.session, channel);
return ChannelFactory.fromGuildChannel(this.session, channel);
}
/**
@ -832,6 +872,10 @@ export class ThreadChannel extends GuildChannel implements Model {
if (data.member) {
this.member = new ThreadMember(session, data.member);
}
if (data.total_message_sent) { this.totalMessageSent = data.total_message_sent; }
if (data.applied_tags) { this.appliedTags = data.applied_tags; }
}
override type: ChannelTypes.GuildNewsThread | ChannelTypes.GuildPrivateThread | ChannelTypes.GuildPublicThread;
@ -843,6 +887,8 @@ export class ThreadChannel extends GuildChannel implements Model {
memberCount?: number;
member?: ThreadMember;
ownerId?: Snowflake;
totalMessageSent?: number;
appliedTags?: string[];
async joinThread(): Promise<void> {
await this.session.rest.put<undefined>(THREAD_ME(this.id), {});
@ -871,12 +917,76 @@ export class ThreadChannel extends GuildChannel implements Model {
return members.map(threadMember => new ThreadMember(this.session, threadMember));
}
async setAppliedTags(tags: string[]) {
const thread = await this.edit({ appliedTags: tags });
return thread;
}
}
export interface ThreadChannel extends Omit<GuildChannel, 'type'>, Omit<TextChannel, 'type'> { }
TextChannel.applyTo(ThreadChannel);
/** ForumChannel */
export class ForumChannel extends GuildChannel {
constructor(session: Session, data: DiscordChannel, guildId: Snowflake) {
super(session, data, guildId);
if (data.available_tags) {
this.availableTags = data.available_tags.map(at => {
return {
id: at.id,
name: at.name,
moderated: at.moderated,
emojiId: at.emoji_id,
emojiName: at.emoji_name
};
});
}
if (data.default_reaction_emoji) {
this.defaultReactionEmoji = {
emojiId: data.default_reaction_emoji.emoji_id,
emojiName: data.default_reaction_emoji.emoji_name
};
}
this.defaultThreadRateLimitPerUser = data.default_thread_rate_limit_per_user;
}
availableTags?: ForumTag[];
defaultReactionEmoji?: DefaultReactionEmoji;
defaultThreadRateLimitPerUser?: number;
async setAvailableTags(tags: ForumTag[]) {
const forum = await this.edit({ availableTags: tags });
return forum;
}
async setDefaultReactionEmoji(emoji: DefaultReactionEmoji) {
const forum = await this.edit({ defaultReactionEmoji: emoji });
return forum;
}
async setDefaultThreadRateLimitPerUser(limit: number) {
const forum = await this.edit({ defaultThreadRateLimitPerUser: limit });
return forum;
}
}
export interface ForumTag {
id: Snowflake;
name: string;
moderated: boolean;
emojiId: Snowflake | null;
emojiName: string | null;
}
export interface DefaultReactionEmoji {
emojiId: Snowflake;
emojiName: string | null;
}
export class GuildTextChannel extends GuildChannel {
constructor(session: Session, data: DiscordChannel, guildId: Snowflake) {
super(session, data, guildId);
@ -899,14 +1009,16 @@ export type Channel =
| NewsChannel
| ThreadChannel
| StageChannel
| CategoryChannel;
| CategoryChannel
| ForumChannel;
export type ChannelInGuild =
| GuildTextChannel
| VoiceChannel
| StageChannel
| NewsChannel
| ThreadChannel;
| ThreadChannel
| ForumChannel;
export type ChannelWithMessages =
| GuildTextChannel
@ -929,6 +1041,8 @@ export class ChannelFactory {
case ChannelTypes.GuildPublicThread:
case ChannelTypes.GuildPrivateThread:
return new ThreadChannel(session, channel, channel.guild_id!);
case ChannelTypes.GuildForum:
return new ForumChannel(session, channel, channel.guild_id!);
case ChannelTypes.GuildText:
return new GuildTextChannel(session, channel, channel.guild_id!);
case ChannelTypes.GuildNews:
@ -947,6 +1061,8 @@ export class ChannelFactory {
case ChannelTypes.GuildPublicThread:
case ChannelTypes.GuildPrivateThread:
return new ThreadChannel(session, channel, channel.guild_id!);
case ChannelTypes.GuildForum:
return new ForumChannel(session, channel, channel.guild_id!);
case ChannelTypes.GuildText:
return new GuildTextChannel(session, channel, channel.guild_id!);
case ChannelTypes.GuildNews:

View File

@ -166,6 +166,8 @@ export class Message implements Model {
);
this.embeds = data.embeds.map(NewEmbedR);
if (data.position) { this.position = data.position; }
if (data.interaction) {
this.interaction = InteractionFactory.fromMessage(
session,
@ -349,6 +351,9 @@ export class Message implements Model {
/** sent with Rich Presence-related chat embeds */
activity?: MessageActivity;
/** Represents the approximate position of the message in a thread */
position?: number;
/** gets the timestamp of this message, this does not requires the timestamp field */
get createdTimestamp(): number {
return Snowflake.snowflakeToTimestamp(this.id);