diff --git a/handlers/Actions.ts b/handlers/Actions.ts index 9d97992..d931398 100644 --- a/handlers/Actions.ts +++ b/handlers/Actions.ts @@ -16,7 +16,7 @@ import type { import type { Snowflake } from "../util/Snowflake.ts"; import type { Session } from "../session/Session.ts"; import type { Channel } from "../structures/BaseChannel.ts"; -import BaseChannel from "../structures/BaseChannel.ts"; +import ChannelFactory from "../structures/ChannelFactory.ts"; import GuildChannel from "../structures/GuildChannel.ts"; import ThreadChannel from "../structures/ThreadChannel.ts"; import Member from "../structures/Member.ts"; @@ -67,11 +67,11 @@ export const INTERACTION_CREATE: RawHandler = (session, _sha }; export const CHANNEL_CREATE: RawHandler = (session, _shardId, channel) => { - session.emit("channelCreate", BaseChannel.from(session, channel)); + session.emit("channelCreate", ChannelFactory.from(session, channel)); }; export const CHANNEL_UPDATE: RawHandler = (session, _shardId, channel) => { - session.emit("channelUpdate", BaseChannel.from(session, channel)); + session.emit("channelUpdate", ChannelFactory.from(session, channel)); }; export const CHANNEL_DELETE: RawHandler = (session, _shardId, channel) => { diff --git a/structures/BaseChannel.ts b/structures/BaseChannel.ts index 7a39ab9..7c9602d 100644 --- a/structures/BaseChannel.ts +++ b/structures/BaseChannel.ts @@ -2,19 +2,13 @@ import type { Model } from "./Base.ts"; import type { Snowflake } from "../util/Snowflake.ts"; import type { Session } from "../session/Session.ts"; import type { DiscordChannel } from "../vendor/external.ts"; +import type TextChannel from "./TextChannel.ts"; +import type VoiceChannel from "./VoiceChannel.ts"; +import type DMChannel from "./DMChannel.ts"; +import type NewsChannel from "./NewsChannel.ts"; +import type ThreadChannel from "./ThreadChannel.ts"; import { ChannelTypes } from "../vendor/external.ts"; -import TextChannel, { textBasedChannels } from "./TextChannel.ts"; -import VoiceChannel from "./VoiceChannel.ts"; -import DMChannel from "./DMChannel.ts"; -import NewsChannel from "./NewsChannel.ts"; -import ThreadChannel from "./ThreadChannel.ts"; - -export type Channel = - | TextChannel - | VoiceChannel - | DMChannel - | NewsChannel - | ThreadChannel; +import { textBasedChannels } from "./TextChannel.ts"; export abstract class BaseChannel implements Model { constructor(session: Session, data: DiscordChannel) { @@ -49,28 +43,11 @@ export abstract class BaseChannel implements Model { return this.type === ChannelTypes.GuildPublicThread || this.type === ChannelTypes.GuildPrivateThread; } - static from(session: Session, channel: DiscordChannel): Channel { - switch (channel.type) { - case ChannelTypes.GuildPublicThread: - case ChannelTypes.GuildPrivateThread: - return new ThreadChannel(session, channel, channel.guild_id!); - case ChannelTypes.GuildNews: - return new NewsChannel(session, channel, channel.guild_id!); - case ChannelTypes.DM: - return new DMChannel(session, channel); - case ChannelTypes.GuildVoice: - return new VoiceChannel(session, channel, channel.guild_id!); - default: - if (textBasedChannels.includes(channel.type)) { - return new TextChannel(session, channel); - } - throw new Error("Channel was not implemented"); - } - } - toString(): string { return `<#${this.id}>`; } } + + export default BaseChannel; diff --git a/structures/ChannelFactory.ts b/structures/ChannelFactory.ts new file mode 100644 index 0000000..be073ff --- /dev/null +++ b/structures/ChannelFactory.ts @@ -0,0 +1,39 @@ +import type { Session } from "../session/Session.ts"; +import type { DiscordChannel } from "../vendor/external.ts"; +import { ChannelTypes } from "../vendor/external.ts"; +import { textBasedChannels } from "./TextChannel.ts"; +import TextChannel from "./TextChannel.ts"; +import VoiceChannel from "./VoiceChannel.ts"; +import DMChannel from "./DMChannel.ts"; +import NewsChannel from "./NewsChannel.ts"; +import ThreadChannel from "./ThreadChannel.ts"; + +export type Channel = + | TextChannel + | VoiceChannel + | DMChannel + | NewsChannel + | ThreadChannel; + +export class ChannelFactory { + static from(session: Session, channel: DiscordChannel): Channel { + switch (channel.type) { + case ChannelTypes.GuildPublicThread: + case ChannelTypes.GuildPrivateThread: + return new ThreadChannel(session, channel, channel.guild_id!); + case ChannelTypes.GuildNews: + return new NewsChannel(session, channel, channel.guild_id!); + case ChannelTypes.DM: + return new DMChannel(session, channel); + case ChannelTypes.GuildVoice: + return new VoiceChannel(session, channel, channel.guild_id!); + default: + if (textBasedChannels.includes(channel.type)) { + return new TextChannel(session, channel); + } + throw new Error("Channel was not implemented"); + } + } +} + +export default ChannelFactory; diff --git a/structures/DMChannel.ts b/structures/DMChannel.ts index 1045c67..d8006c0 100644 --- a/structures/DMChannel.ts +++ b/structures/DMChannel.ts @@ -1,11 +1,13 @@ import type { Model } from "./Base.ts"; import type { Session } from "../session/Session.ts"; +import type { Snowflake } from "../util/Snowflake.ts"; import type { ChannelTypes, DiscordChannel } from "../vendor/external.ts"; import TextChannel from "./TextChannel.ts"; +import BaseChannel from "./BaseChannel.ts"; import User from "./User.ts"; import * as Routes from "../util/Routes.ts"; -export class DMChannel extends TextChannel implements Model { +export class DMChannel extends BaseChannel implements Model { constructor(session: Session, data: DiscordChannel) { super(session, data); @@ -18,6 +20,7 @@ export class DMChannel extends TextChannel implements Model { override type: ChannelTypes.DM | ChannelTypes.GroupDm; user: User; + lastMessageId?: Snowflake; async close() { const channel = await this.session.rest.runMethod( @@ -30,4 +33,8 @@ export class DMChannel extends TextChannel implements Model { } } +TextChannel.applyTo(DMChannel); + +export interface DMChannel extends TextChannel, BaseChannel {} + export default DMChannel; diff --git a/structures/GuildChannel.ts b/structures/GuildChannel.ts index 58065bc..19aa931 100644 --- a/structures/GuildChannel.ts +++ b/structures/GuildChannel.ts @@ -1,21 +1,11 @@ import type { Model } from "./Base.ts"; import type { Snowflake } from "../util/Snowflake.ts"; import type { Session } from "../session/Session.ts"; -import type { ChannelTypes, DiscordChannel, DiscordWebhook, DiscordInviteMetadata } from "../vendor/external.ts"; -import { urlToBase64 } from "../util/urlToBase64.ts"; +import type { ChannelTypes, DiscordChannel, DiscordInviteMetadata } from "../vendor/external.ts"; import BaseChannel from "./BaseChannel.ts"; import Invite from "./Invite.ts"; -import Webhook from "./Webhook.ts"; -import ThreadChannel from "./ThreadChannel.ts"; import * as Routes from "../util/Routes.ts"; -export interface CreateWebhook { - name: string; - avatar?: string; - reason?: string; -} - - /** * Represent the options object to create a Thread Channel * @link https://discord.com/developers/docs/resources/channel#start-thread-without-message @@ -31,7 +21,7 @@ export interface ThreadCreateOptions { export class GuildChannel extends BaseChannel implements Model { constructor(session: Session, data: DiscordChannel, guildId: Snowflake) { super(session, data); - this.type = data.type as number + this.type = data.type as number; this.guildId = guildId; this.position = data.position; data.topic ? this.topic = data.topic : null; @@ -54,6 +44,17 @@ export class GuildChannel extends BaseChannel implements Model { return invites.map((invite) => new Invite(this.session, invite)); } + /* + async createThread(options: ThreadCreateOptions): Promise { + const thread = await this.session.rest.runMethod( + this.session.rest, + "POST", + Routes.CHANNEL_CREATE_THREAD(this.id), + options, + ); + return new ThreadChannel(this.session, thread, this.guildId); + }*/ + async delete(reason?: string) { await this.session.rest.runMethod( this.session.rest, @@ -64,31 +65,6 @@ export class GuildChannel extends BaseChannel implements Model { }, ); } - - async createThread(options: ThreadCreateOptions): Promise { - const thread = await this.session.rest.runMethod( - this.session.rest, - "POST", - Routes.CHANNEL_CREATE_THREAD(this.id), - options, - ); - return new ThreadChannel(this.session, thread, this.guildId); - } - - async createWebhook(options: CreateWebhook) { - const webhook = await this.session.rest.runMethod( - this.session.rest, - "POST", - Routes.CHANNEL_WEBHOOKS(this.id), - { - name: options.name, - avatar: options.avatar ? urlToBase64(options.avatar) : undefined, - reason: options.reason, - } - ); - - return new Webhook(this.session, webhook); - } } export default GuildChannel; diff --git a/structures/NewsChannel.ts b/structures/NewsChannel.ts index e0b7dad..2dbebbb 100644 --- a/structures/NewsChannel.ts +++ b/structures/NewsChannel.ts @@ -3,6 +3,7 @@ import type { Session } from "../session/Session.ts"; import type { ChannelTypes, DiscordChannel } from "../vendor/external.ts"; import GuildChannel from "./GuildChannel.ts"; import Message from "./Message.ts"; +import TextChannel from "./TextChannel.ts"; export class NewsChannel extends GuildChannel { constructor(session: Session, data: DiscordChannel, guildId: Snowflake) { @@ -23,4 +24,8 @@ export class NewsChannel extends GuildChannel { } } +TextChannel.applyTo(NewsChannel); + +export interface NewsChannel extends TextChannel, GuildChannel {} + export default NewsChannel; diff --git a/structures/TextChannel.ts b/structures/TextChannel.ts index c2ea25f..3699583 100644 --- a/structures/TextChannel.ts +++ b/structures/TextChannel.ts @@ -1,12 +1,14 @@ +// deno-lint-ignore-file ban-types import type { Session } from "../session/Session.ts"; import type { Snowflake } from "../util/Snowflake.ts"; import type { GetMessagesOptions, GetReactions } from "../util/Routes.ts"; -import type { DiscordChannel, DiscordInvite, DiscordMessage, TargetTypes } from "../vendor/external.ts"; +import type { DiscordChannel, DiscordInvite, DiscordMessage, DiscordWebhook, TargetTypes } from "../vendor/external.ts"; import type { CreateMessage, EditMessage, ReactionResolvable } from "./Message.ts"; import { ChannelTypes } from "../vendor/external.ts"; -import BaseChannel from "./BaseChannel.ts"; +import { urlToBase64 } from "../util/urlToBase64.ts"; import Message from "./Message.ts"; import Invite from "./Invite.ts"; +import Webhook from "./Webhook.ts"; import * as Routes from "../util/Routes.ts"; /** @@ -24,6 +26,12 @@ export interface DiscordInviteOptions { targetApplicationId?: Snowflake; } +export interface CreateWebhook { + name: string; + avatar?: string; + reason?: string; +} + export const textBasedChannels = [ ChannelTypes.DM, ChannelTypes.GroupDm, @@ -41,9 +49,11 @@ export type TextBasedChannels = | ChannelTypes.GuildNews | ChannelTypes.GuildText; -export class TextChannel extends BaseChannel { +export class TextChannel { constructor(session: Session, data: DiscordChannel) { - super(session, data); + this.session = session; + this.id = data.id; + this.name = data.name; this.type = data.type as number; this.rateLimitPerUser = data.rate_limit_per_user ?? 0; this.nsfw = !!data.nsfw ?? false; @@ -57,12 +67,35 @@ export class TextChannel extends BaseChannel { } } - override type: TextBasedChannels; + readonly session: Session; + readonly id: Snowflake; + name?: string; + type: TextBasedChannels; lastMessageId?: Snowflake; lastPinTimestamp?: string; rateLimitPerUser: number; nsfw: boolean; + /** + * Mixin + * */ + static applyTo(klass: Function) { + klass.prototype.fetchPins = TextChannel.prototype.fetchPins; + klass.prototype.createInvite = TextChannel.prototype.createInvite; + klass.prototype.fetchMessages = TextChannel.prototype.fetchMessages; + klass.prototype.sendTyping = TextChannel.prototype.sendTyping; + klass.prototype.pinMessage = TextChannel.prototype.pinMessage; + klass.prototype.unpinMessage = TextChannel.prototype.unpinMessage; + klass.prototype.addReaction = TextChannel.prototype.addReaction; + klass.prototype.removeReaction = TextChannel.prototype.removeReaction; + klass.prototype.removeReactionEmoji = TextChannel.prototype.removeReactionEmoji; + klass.prototype.nukeReactions = TextChannel.prototype.nukeReactions; + klass.prototype.fetchReactions = TextChannel.prototype.fetchReactions; + klass.prototype.sendMessage = TextChannel.prototype.sendMessage; + klass.prototype.editMessage = TextChannel.prototype.editMessage; + klass.prototype.createWebhook = TextChannel.prototype.createWebhook; + } + async fetchPins(): Promise { const messages = await this.session.rest.runMethod( this.session.rest, @@ -162,6 +195,22 @@ export class TextChannel extends BaseChannel { editMessage(messageId: Snowflake, options: EditMessage) { return Message.prototype.edit.call({ channelId: this.id, id: messageId, session: this.session }, options); } + + + async createWebhook(options: CreateWebhook) { + const webhook = await this.session.rest.runMethod( + this.session.rest, + "POST", + Routes.CHANNEL_WEBHOOKS(this.id), + { + name: options.name, + avatar: options.avatar ? urlToBase64(options.avatar) : undefined, + reason: options.reason, + } + ); + + return new Webhook(this.session, webhook); + } } export default TextChannel; diff --git a/structures/ThreadChannel.ts b/structures/ThreadChannel.ts index b846f93..a6073eb 100644 --- a/structures/ThreadChannel.ts +++ b/structures/ThreadChannel.ts @@ -1,12 +1,14 @@ import type { Model } from "./Base.ts"; import type { Snowflake } from "../util/Snowflake.ts"; import type { Session } from "../session/Session.ts"; -import type { DiscordChannel } from "../vendor/external.ts"; +import type { ChannelTypes, DiscordChannel } from "../vendor/external.ts"; import GuildChannel from "./GuildChannel.ts"; +import TextChannel from "./TextChannel.ts"; export class ThreadChannel extends GuildChannel implements Model { constructor(session: Session, data: DiscordChannel, guildId: Snowflake) { super(session, data, guildId); + this.type = data.type as number; this.archived = !!data.thread_metadata?.archived; this.archiveTimestamp = data.thread_metadata?.archive_timestamp; this.autoArchiveDuration = data.thread_metadata?.auto_archive_duration; @@ -15,6 +17,8 @@ export class ThreadChannel extends GuildChannel implements Model { this.memberCount = data.member_count; this.ownerId = data.owner_id; } + + override type: ChannelTypes.GuildNewsThread | ChannelTypes.GuildPrivateThread | ChannelTypes.GuildPublicThread; archived?: boolean; archiveTimestamp?: string; autoArchiveDuration?: number; @@ -24,4 +28,8 @@ export class ThreadChannel extends GuildChannel implements Model { ownerId?: Snowflake; } +TextChannel.applyTo(ThreadChannel); + +export interface ThreadChannel extends Omit, Omit {} + export default ThreadChannel; diff --git a/structures/components/Component.ts b/structures/components/Component.ts index 4bb2cf0..ffda874 100644 --- a/structures/components/Component.ts +++ b/structures/components/Component.ts @@ -1,15 +1,6 @@ -import type { Session } from "../../session/Session.ts"; -import type { DiscordComponent } from "../../vendor/external.ts"; import type Emoji from "../Emoji.ts"; import { ButtonStyles, MessageComponentTypes, TextStyles } from "../../vendor/external.ts"; -// TODO: fix circular dependencies -import ActionRow from "./ActionRowComponent.ts"; -import Button from "./ButtonComponent.ts"; -import LinkButton from "./ButtonComponent.ts"; -import SelectMenu from "./SelectMenuComponent.ts"; -import TextInput from "./TextInputComponent.ts"; - export class BaseComponent { constructor(type: MessageComponentTypes) { this.type = type; @@ -33,25 +24,6 @@ export class BaseComponent { return this.type === MessageComponentTypes.InputText; } - /** - * Component factory - * @internal - * */ - static from(session: Session, component: DiscordComponent): Component { - switch (component.type) { - case MessageComponentTypes.ActionRow: - return new ActionRow(session, component); - case MessageComponentTypes.Button: - if (component.style === ButtonStyles.Link) { - return new LinkButton(session, component); - } - return new Button(session, component); - case MessageComponentTypes.SelectMenu: - return new SelectMenu(session, component); - case MessageComponentTypes.InputText: - return new TextInput(session, component); - } - } } /** Action Row Component */ diff --git a/structures/components/ComponentFactory.ts b/structures/components/ComponentFactory.ts new file mode 100644 index 0000000..4ef8d2b --- /dev/null +++ b/structures/components/ComponentFactory.ts @@ -0,0 +1,33 @@ +import type { Session } from "../../session/Session.ts"; +import type { DiscordComponent } from "../../vendor/external.ts"; +import type { Component } from "./Component.ts"; +import { MessageComponentTypes, ButtonStyles } from "../../vendor/external.ts"; +import ActionRow from "./ActionRowComponent.ts"; +import Button from "./ButtonComponent.ts"; +import LinkButton from "./ButtonComponent.ts"; +import SelectMenu from "./SelectMenuComponent.ts"; +import TextInput from "./TextInputComponent.ts"; + +export class ComponentFactory { + /** + * Component factory + * @internal + * */ + static from(session: Session, component: DiscordComponent): Component { + switch (component.type) { + case MessageComponentTypes.ActionRow: + return new ActionRow(session, component); + case MessageComponentTypes.Button: + if (component.style === ButtonStyles.Link) { + return new LinkButton(session, component); + } + return new Button(session, component); + case MessageComponentTypes.SelectMenu: + return new SelectMenu(session, component); + case MessageComponentTypes.InputText: + return new TextInput(session, component); + } + } +} + +export default ComponentFactory;