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; 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 */ /** https://discord.com/developers/docs/resources/guild#guild-object-guild-features */
export enum GuildFeatures { export enum GuildFeatures {
/** Guild has access to set an invite splash background */ /** Guild has access to set an invite splash background */

View File

@ -12,6 +12,7 @@ import type {
DefaultMessageNotificationLevels, DefaultMessageNotificationLevels,
EmbedTypes, EmbedTypes,
ExplicitContentFilterLevels, ExplicitContentFilterLevels,
DiscordForumTag,
GatewayEventNames, GatewayEventNames,
GuildFeatures, GuildFeatures,
GuildNsfwLevel, GuildNsfwLevel,
@ -754,6 +755,17 @@ export interface DiscordChannel {
newly_created?: boolean; newly_created?: boolean;
/** The recipients of the DM*/ /** The recipients of the DM*/
recipients?: DiscordUser[]; 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 */ /** https://discord.com/developers/docs/topics/gateway#presence-update */
@ -1051,6 +1063,8 @@ export interface DiscordMessage {
components?: DiscordMessageComponents; components?: DiscordMessageComponents;
/** Sent if the message contains stickers */ /** Sent if the message contains stickers */
sticker_items?: DiscordStickerItem[]; 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 */ /** 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; 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> { async fetch(channelId?: Snowflake): Promise<Channel> {
const channel = await this.session.rest.get<DiscordChannel>(CHANNEL(channelId ?? this.id)); const channel = await this.session.rest.get<DiscordChannel>(CHANNEL(channelId ?? this.id));
@ -506,10 +511,20 @@ export interface EditNewsChannelOptions extends EditGuildChannelOptions {
defaultAutoArchiveDuration?: number | null; defaultAutoArchiveDuration?: number | null;
} }
export interface EditForumChannelOptions extends EditGuildChannelOptions {
availableTags?: ForumTag[];
defaultReactionEmoji?: DefaultReactionEmoji;
defaultThreadRateLimitPerUser?: number;
}
export interface EditGuildTextChannelOptions extends EditNewsChannelOptions { export interface EditGuildTextChannelOptions extends EditNewsChannelOptions {
rateLimitPerUser?: number | null; rateLimitPerUser?: number | null;
} }
export interface EditThreadChannelOptions extends EditGuildTextChannelOptions {
appliedTags: string[];
}
export interface EditStageChannelOptions extends EditGuildChannelOptions { export interface EditStageChannelOptions extends EditGuildChannelOptions {
bitrate?: number | null; bitrate?: number | null;
rtcRegion?: Snowflake | null; rtcRegion?: Snowflake | null;
@ -589,9 +604,16 @@ export class GuildChannel extends BaseChannel implements Model {
async edit(options: EditNewsChannelOptions): Promise<NewsChannel>; async edit(options: EditNewsChannelOptions): Promise<NewsChannel>;
async edit(options: EditStageChannelOptions): Promise<StageChannel>; async edit(options: EditStageChannelOptions): Promise<StageChannel>;
async edit(options: EditVoiceChannelOptions): Promise<VoiceChannel>; async edit(options: EditVoiceChannelOptions): Promise<VoiceChannel>;
async edit(options: EditForumChannelOptions): Promise<ForumChannel>;
async edit(options: EditThreadChannelOptions): Promise<ThreadChannel>;
async edit( async edit(
options: EditGuildTextChannelOptions | EditNewsChannelOptions | EditVoiceChannelOptions, options:
): Promise<Channel> { | EditGuildTextChannelOptions
| EditNewsChannelOptions
| EditVoiceChannelOptions
| EditForumChannelOptions
| EditThreadChannelOptions
): Promise<GuildChannel> {
const channel = await this.session.rest.patch<DiscordChannel>( const channel = await this.session.rest.patch<DiscordChannel>(
CHANNEL(this.id), CHANNEL(this.id),
{ {
@ -607,12 +629,30 @@ export class GuildChannel extends BaseChannel implements Model {
parent_id: 'parentId' in options ? options.parentId : undefined, parent_id: 'parentId' in options ? options.parentId : undefined,
rtc_region: 'rtcRegion' in options ? options.rtcRegion : undefined, rtc_region: 'rtcRegion' in options ? options.rtcRegion : undefined,
video_quality_mode: 'videoQualityMode' in options ? options.videoQualityMode : 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 default_auto_archive_duration: 'defaultAutoArchiveDuration' in options
? options.defaultAutoArchiveDuration ? options.defaultAutoArchiveDuration
: undefined, : 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) { if (data.member) {
this.member = new ThreadMember(session, 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; override type: ChannelTypes.GuildNewsThread | ChannelTypes.GuildPrivateThread | ChannelTypes.GuildPublicThread;
@ -843,6 +887,8 @@ export class ThreadChannel extends GuildChannel implements Model {
memberCount?: number; memberCount?: number;
member?: ThreadMember; member?: ThreadMember;
ownerId?: Snowflake; ownerId?: Snowflake;
totalMessageSent?: number;
appliedTags?: string[];
async joinThread(): Promise<void> { async joinThread(): Promise<void> {
await this.session.rest.put<undefined>(THREAD_ME(this.id), {}); 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)); 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'> { } export interface ThreadChannel extends Omit<GuildChannel, 'type'>, Omit<TextChannel, 'type'> { }
TextChannel.applyTo(ThreadChannel); 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 { export class GuildTextChannel extends GuildChannel {
constructor(session: Session, data: DiscordChannel, guildId: Snowflake) { constructor(session: Session, data: DiscordChannel, guildId: Snowflake) {
super(session, data, guildId); super(session, data, guildId);
@ -899,14 +1009,16 @@ export type Channel =
| NewsChannel | NewsChannel
| ThreadChannel | ThreadChannel
| StageChannel | StageChannel
| CategoryChannel; | CategoryChannel
| ForumChannel;
export type ChannelInGuild = export type ChannelInGuild =
| GuildTextChannel | GuildTextChannel
| VoiceChannel | VoiceChannel
| StageChannel | StageChannel
| NewsChannel | NewsChannel
| ThreadChannel; | ThreadChannel
| ForumChannel;
export type ChannelWithMessages = export type ChannelWithMessages =
| GuildTextChannel | GuildTextChannel
@ -929,6 +1041,8 @@ export class ChannelFactory {
case ChannelTypes.GuildPublicThread: case ChannelTypes.GuildPublicThread:
case ChannelTypes.GuildPrivateThread: case ChannelTypes.GuildPrivateThread:
return new ThreadChannel(session, channel, channel.guild_id!); return new ThreadChannel(session, channel, channel.guild_id!);
case ChannelTypes.GuildForum:
return new ForumChannel(session, channel, channel.guild_id!);
case ChannelTypes.GuildText: case ChannelTypes.GuildText:
return new GuildTextChannel(session, channel, channel.guild_id!); return new GuildTextChannel(session, channel, channel.guild_id!);
case ChannelTypes.GuildNews: case ChannelTypes.GuildNews:
@ -947,6 +1061,8 @@ export class ChannelFactory {
case ChannelTypes.GuildPublicThread: case ChannelTypes.GuildPublicThread:
case ChannelTypes.GuildPrivateThread: case ChannelTypes.GuildPrivateThread:
return new ThreadChannel(session, channel, channel.guild_id!); return new ThreadChannel(session, channel, channel.guild_id!);
case ChannelTypes.GuildForum:
return new ForumChannel(session, channel, channel.guild_id!);
case ChannelTypes.GuildText: case ChannelTypes.GuildText:
return new GuildTextChannel(session, channel, channel.guild_id!); return new GuildTextChannel(session, channel, channel.guild_id!);
case ChannelTypes.GuildNews: case ChannelTypes.GuildNews:

View File

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