diff --git a/handlers/Actions.ts b/handlers/Actions.ts index 032853c..5975a4b 100644 --- a/handlers/Actions.ts +++ b/handlers/Actions.ts @@ -1,18 +1,28 @@ import type { + DiscordChannel, + DiscordChannelPinsUpdate, DiscordGuildMemberAdd, DiscordGuildMemberRemove, DiscordGuildMemberUpdate, DiscordInteraction, + DiscordMemberWithUser, DiscordMessage, DiscordMessageDelete, DiscordReady, + // DiscordThreadMemberUpdate, + // DiscordThreadMembersUpdate, + DiscordThreadListSync, } from "../vendor/external.ts"; import type { Snowflake } from "../util/Snowflake.ts"; import type { Session } from "../session/Session.ts"; +import type { Channel } from "../structures/channels/ChannelFactory.ts"; +import ChannelFactory from "../structures/channels/ChannelFactory.ts"; +import GuildChannel from "../structures/channels/GuildChannel.ts"; +import ThreadChannel from "../structures/channels/ThreadChannel.ts"; import Member from "../structures/Member.ts"; import Message from "../structures/Message.ts"; import User from "../structures/User.ts"; -import Interaction from "../structures/Interaction.ts"; +import Interaction from "../structures/interactions/Interaction.ts"; export type RawHandler = (...args: [Session, number, T]) => void; export type Handler = (...args: T) => unknown; @@ -56,6 +66,58 @@ export const INTERACTION_CREATE: RawHandler = (session, _sha session.emit("interactionCreate", new Interaction(session, interaction)); }; +export const CHANNEL_CREATE: RawHandler = (session, _shardId, channel) => { + session.emit("channelCreate", ChannelFactory.from(session, channel)); +}; + +export const CHANNEL_UPDATE: RawHandler = (session, _shardId, channel) => { + session.emit("channelUpdate", ChannelFactory.from(session, channel)); +}; + +export const CHANNEL_DELETE: RawHandler = (session, _shardId, channel) => { + if (!channel.guild_id) return; + + session.emit("channelDelete", new GuildChannel(session, channel, channel.guild_id)); +}; + +export const THREAD_CREATE: RawHandler = (session, _shardId, channel) => { + if (!channel.guild_id) return; + + session.emit("threadCreate", new ThreadChannel(session, channel, channel.guild_id)); +}; + +export const THREAD_UPDATE: RawHandler = (session, _shardId, channel) => { + if (!channel.guild_id) return; + + session.emit("threadUpdate", new ThreadChannel(session, channel, channel.guild_id)); +}; + +export const THREAD_DELETE: RawHandler = (session, _shardId, channel) => { + if (!channel.guild_id) return; + + session.emit("threadDelete", new ThreadChannel(session, channel, channel.guild_id)); +}; + +export const THREAD_LIST_SYNC: RawHandler = (session, _shardId, payload) => { + session.emit("threadListSync", { + guildId: payload.guild_id, + channelIds: payload.channel_ids ?? [], + threads: payload.threads.map((channel) => new ThreadChannel(session, channel, payload.guild_id)), + members: payload.members.map((member) => + // @ts-ignore: TODO: thread member structure + new Member(session, member as DiscordMemberWithUser, payload.guild_id) + ), + }); +}; + +export const CHANNEL_PINS_UPDATE: RawHandler = (session, _shardId, payload) => { + session.emit("channelPinsUpdate", { + guildId: payload.guild_id, + channelId: payload.channel_id, + lastPinTimestamp: payload.last_pin_timestamp ? Date.parse(payload.last_pin_timestamp) : undefined, + }); +}; + export const raw: RawHandler = (session, shardId, data) => { session.emit("raw", data, shardId); }; @@ -73,6 +135,14 @@ export interface Events { "guildMemberAdd": Handler<[Member]>; "guildMemberUpdate": Handler<[Member]>; "guildMemberRemove": Handler<[User, Snowflake]>; + "channelCreate": Handler<[Channel]>; + "channelUpdate": Handler<[Channel]>; + "channelDelete": Handler<[GuildChannel]>; + "channelPinsUpdate": Handler<[{ guildId?: Snowflake, channelId: Snowflake, lastPinTimestamp?: number }]> + "threadCreate": Handler<[ThreadChannel]>; + "threadUpdate": Handler<[ThreadChannel]>; + "threadDelete": Handler<[ThreadChannel]>; + "threadListSync": Handler<[{ guildId: Snowflake, channelIds: Snowflake[], threads: ThreadChannel[], members: Member[] }]> "interactionCreate": Handler<[Interaction]>; "raw": Handler<[unknown, number]>; } diff --git a/mod.ts b/mod.ts index 8e72101..f7d725a 100644 --- a/mod.ts +++ b/mod.ts @@ -1,30 +1,40 @@ -export * from "./structures/AnonymousGuild.ts"; export * from "./structures/Attachment.ts"; export * from "./structures/Base.ts"; -export * from "./structures/BaseGuild.ts"; -export * from "./structures/BaseChannel.ts"; -export * from "./structures/Component.ts"; -export * from "./structures/DMChannel.ts"; export * from "./structures/Embed.ts"; export * from "./structures/Emoji.ts"; -export * from "./structures/Guild.ts"; -export * from "./structures/GuildChannel.ts"; export * from "./structures/GuildEmoji.ts"; -export * from "./structures/Interaction.ts"; export * from "./structures/Invite.ts"; -export * from "./structures/InviteGuild.ts"; export * from "./structures/Member.ts"; export * from "./structures/Message.ts"; -export * from "./structures/NewsChannel.ts"; export * from "./structures/Permissions.ts"; export * from "./structures/Role.ts"; -export * from "./structures/TextChannel.ts"; -export * from "./structures/ThreadChannel.ts"; export * from "./structures/User.ts"; -export * from "./structures/VoiceChannel.ts"; export * from "./structures/WelcomeChannel.ts"; export * from "./structures/WelcomeScreen.ts"; +export * from "./structures/channels/BaseChannel.ts"; +export * from "./structures/channels/ChannelFactory.ts"; +export * from "./structures/channels/DMChannel.ts"; +export * from "./structures/channels/GuildChannel.ts"; +export * from "./structures/channels/NewsChannel.ts"; +export * from "./structures/channels/TextChannel.ts"; +export * from "./structures/channels/ThreadChannel.ts"; +export * from "./structures/channels/VoiceChannel.ts"; + +export * from "./structures/components/ActionRowComponent.ts"; +export * from "./structures/components/ButtonComponent.ts"; +export * from "./structures/components/Component.ts"; +export * from "./structures/components/LinkButtonComponent.ts"; +export * from "./structures/components/SelectMenuComponent.ts"; +export * from "./structures/components/TextInputComponent.ts"; + +export * from "./structures/guilds/AnonymousGuild.ts"; +export * from "./structures/guilds/BaseGuild.ts"; +export * from "./structures/guilds/Guild.ts"; +export * from "./structures/guilds/InviteGuild.ts"; + +export * from "./structures/interactions/Interaction.ts"; + export * from "./session/Session.ts"; export * from "./util/shared/flags.ts"; diff --git a/session/Session.ts b/session/Session.ts index 10e74e4..6b25993 100644 --- a/session/Session.ts +++ b/session/Session.ts @@ -49,7 +49,7 @@ export class Session extends EventEmitter { } get applicationId() { - return this.#applicationId!; + return this.#applicationId!; } set botId(id: Snowflake) { diff --git a/structures/BaseChannel.ts b/structures/BaseChannel.ts deleted file mode 100644 index a0ed80b..0000000 --- a/structures/BaseChannel.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { Model } from "./Base.ts"; -import type { Snowflake } from "../util/Snowflake.ts"; -import type { Session } from "../session/Session.ts"; -import type { ChannelTypes, DiscordChannel } from "../vendor/external.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 abstract class BaseChannel implements Model { - constructor(session: Session, data: DiscordChannel) { - this.id = data.id; - this.session = session; - this.name = data.name; - this.type = data.type; - } - readonly id: Snowflake; - readonly session: Session; - - name?: string; - type: ChannelTypes; - - isText(): this is TextChannel { - return this instanceof TextChannel; - } - - isVoice(): this is VoiceChannel { - return this instanceof VoiceChannel; - } - - isDM(): this is DMChannel { - return this instanceof DMChannel; - } - - isNews(): this is NewsChannel { - return this instanceof NewsChannel; - } - - isThread(): this is ThreadChannel { - return this instanceof ThreadChannel; - } - - toString(): string { - return `<#${this.id}>`; - } -} - -export default BaseChannel; diff --git a/structures/Component.ts b/structures/Component.ts deleted file mode 100644 index 02daff5..0000000 --- a/structures/Component.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { Session } from "../session/Session.ts"; -import type { DiscordComponent, MessageComponentTypes } from "../vendor/external.ts"; -import Emoji from "./Emoji.ts"; - -export class Component { - constructor(session: Session, data: DiscordComponent) { - this.session = session; - this.customId = data.custom_id; - this.type = data.type; - this.components = data.components?.map((component) => new Component(session, component)); - this.disabled = !!data.disabled; - - if (data.emoji) { - this.emoji = new Emoji(session, data.emoji); - } - - this.maxValues = data.max_values; - this.minValues = data.min_values; - this.label = data.label; - this.value = data.value; - this.options = data.options ?? []; - this.placeholder = data.placeholder; - } - - readonly session: Session; - - customId?: string; - type: MessageComponentTypes; - components?: Component[]; - disabled: boolean; - emoji?: Emoji; - maxValues?: number; - minValues?: number; - label?: string; - value?: string; - // deno-lint-ignore no-explicit-any - options: any[]; - placeholder?: string; -} - -export default Component; diff --git a/structures/GuildChannel.ts b/structures/GuildChannel.ts deleted file mode 100644 index 73dfd3b..0000000 --- a/structures/GuildChannel.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { Model } from "./Base.ts"; -import type { Snowflake } from "../util/Snowflake.ts"; -import type { Session } from "../session/Session.ts"; -import type { DiscordChannel, DiscordInviteMetadata } from "../vendor/external.ts"; -import BaseChannel from "./BaseChannel.ts"; -import Invite from "./Invite.ts"; -import * as Routes from "../util/Routes.ts"; - -export abstract class GuildChannel extends BaseChannel implements Model { - constructor(session: Session, data: DiscordChannel, guildId: Snowflake) { - super(session, data); - this.guildId = guildId; - this.position = data.position; - data.topic ? this.topic = data.topic : null; - data.parent_id ? this.parentId = data.parent_id : undefined; - } - - guildId: Snowflake; - topic?: string; - position?: number; - parentId?: Snowflake; - - async fetchInvites(): Promise { - const invites = await this.session.rest.runMethod( - this.session.rest, - "GET", - Routes.CHANNEL_INVITES(this.id), - ); - - return invites.map((invite) => new Invite(this.session, invite)); - } - - async delete(reason?: string) { - await this.session.rest.runMethod( - this.session.rest, - "DELETE", - Routes.CHANNEL(this.id), - { - reason, - }, - ); - } -} - -export default GuildChannel; diff --git a/structures/GuildEmoji.ts b/structures/GuildEmoji.ts index 0de968d..6b3d2b3 100644 --- a/structures/GuildEmoji.ts +++ b/structures/GuildEmoji.ts @@ -2,8 +2,8 @@ import type { Model } from "./Base.ts"; import type { Snowflake } from "../util/Snowflake.ts"; import type { Session } from "../session/Session.ts"; import type { DiscordEmoji } from "../vendor/external.ts"; -import type { ModifyGuildEmoji } from "./Guild.ts"; -import Guild from "./Guild.ts"; +import type { ModifyGuildEmoji } from "./guilds/Guild.ts"; +import Guild from "./guilds/Guild.ts"; import Emoji from "./Emoji.ts"; import User from "./User.ts"; import * as Routes from "../util/Routes.ts"; diff --git a/structures/Invite.ts b/structures/Invite.ts index 2e12880..5d238e7 100644 --- a/structures/Invite.ts +++ b/structures/Invite.ts @@ -1,9 +1,9 @@ import type { Session } from "../session/Session.ts"; import type { DiscordInvite } from "../vendor/external.ts"; import { TargetTypes } from "../vendor/external.ts"; -import InviteGuild from "./InviteGuild.ts"; +import InviteGuild from "./guilds/InviteGuild.ts"; import User from "./User.ts"; -import Guild from "./Guild.ts"; +import Guild from "./guilds/Guild.ts"; /** * @link https://discord.com/developers/docs/resources/invite#invite-object diff --git a/structures/Member.ts b/structures/Member.ts index 6468ce3..601d5af 100644 --- a/structures/Member.ts +++ b/structures/Member.ts @@ -3,10 +3,10 @@ import type { Snowflake } from "../util/Snowflake.ts"; import type { Session } from "../session/Session.ts"; import type { DiscordMemberWithUser } from "../vendor/external.ts"; import type { ImageFormat, ImageSize } from "../util/shared/images.ts"; -import type { CreateGuildBan, ModifyGuildMember } from "./Guild.ts"; +import type { CreateGuildBan, ModifyGuildMember } from "./guilds/Guild.ts"; import { iconBigintToHash, iconHashToBigInt } from "../util/hash.ts"; import User from "./User.ts"; -import Guild from "./Guild.ts"; +import Guild from "./guilds/Guild.ts"; import * as Routes from "../util/Routes.ts"; /** @@ -87,7 +87,12 @@ export class Member implements Model { } async removeRole(roleId: Snowflake, options: { reason?: string } = {}) { - await Guild.prototype.removeRole.call({ id: this.guildId, session: this.session }, this.user.id, roleId, options); + await Guild.prototype.removeRole.call( + { id: this.guildId, session: this.session }, + this.user.id, + roleId, + options, + ); } /** gets the user's avatar */ diff --git a/structures/Message.ts b/structures/Message.ts index 65f3c35..3d734d8 100644 --- a/structures/Message.ts +++ b/structures/Message.ts @@ -8,11 +8,13 @@ import type { DiscordUser, FileContent, } from "../vendor/external.ts"; +import type { Component } from "./components/Component.ts"; import type { GetReactions } from "../util/Routes.ts"; import { MessageFlags } from "../util/shared/flags.ts"; import User from "./User.ts"; import Member from "./Member.ts"; import Attachment from "./Attachment.ts"; +import BaseComponent from "./components/Component.ts"; import * as Routes from "../util/Routes.ts"; /** @@ -80,6 +82,8 @@ export class Message implements Model { if (data.guild_id && data.member) { this.member = new Member(session, { ...data.member, user: data.author }, data.guild_id); } + + this.components = data.components?.map((component) => BaseComponent.from(session, component)); } readonly session: Session; @@ -95,6 +99,7 @@ export class Message implements Model { attachments: Attachment[]; member?: Member; + components?: Component[]; get url() { return `https://discord.com/channels/${this.guildId ?? "@me"}/${this.channelId}/${this.id}`; diff --git a/structures/Role.ts b/structures/Role.ts index 41a4c97..43ec61d 100644 --- a/structures/Role.ts +++ b/structures/Role.ts @@ -4,7 +4,7 @@ import type { Session } from "../session/Session.ts"; import { Snowflake } from "../util/Snowflake.ts"; import { iconHashToBigInt } from "../util/hash.ts"; import Permissions from "./Permissions.ts"; -import Guild, { ModifyGuildRole } from "./Guild.ts"; +import Guild, { ModifyGuildRole } from "./guilds/Guild.ts"; export class Role implements Model { constructor(session: Session, data: DiscordRole, guildId: Snowflake) { diff --git a/structures/Webhook.ts b/structures/Webhook.ts new file mode 100644 index 0000000..6a85a22 --- /dev/null +++ b/structures/Webhook.ts @@ -0,0 +1,47 @@ +import type { Model } from "./Base.ts"; +import type { Session } from "../session/Session.ts"; +import type { Snowflake } from "../util/Snowflake.ts"; +import type { DiscordWebhook, WebhookTypes } from "../vendor/external.ts"; +import { iconHashToBigInt } from "../util/hash.ts"; +import User from "./User.ts"; + +export class Webhook implements Model { + constructor(session: Session, data: DiscordWebhook) { + this.session = session; + this.id = data.id; + this.type = data.type; + this.token = data.token; + + if (data.avatar) { + this.avatar = iconHashToBigInt(data.avatar); + } + + if (data.user) { + this.user = new User(session, data.user); + } + + if (data.guild_id) { + this.guildId = data.guild_id; + } + + if (data.channel_id) { + this.channelId = data.channel_id; + } + + if (data.application_id) { + this.applicationId = data.application_id; + } + } + + readonly session: Session; + readonly id: Snowflake; + type: WebhookTypes; + token?: string; + avatar?: bigint; + applicationId?: Snowflake; + channelId?: Snowflake; + guildId?: Snowflake; + user?: User; +} + +export default Webhook; diff --git a/structures/WelcomeChannel.ts b/structures/WelcomeChannel.ts index e540207..b1bcb5d 100644 --- a/structures/WelcomeChannel.ts +++ b/structures/WelcomeChannel.ts @@ -5,6 +5,7 @@ import type { DiscordWelcomeScreenChannel } from "../vendor/external.ts"; import Emoji from "./Emoji.ts"; /** + * Not a channel * @link https://discord.com/developers/docs/resources/guild#welcome-screen-object-welcome-screen-channel-structure */ export class WelcomeChannel implements Model { diff --git a/structures/channels/BaseChannel.ts b/structures/channels/BaseChannel.ts new file mode 100644 index 0000000..fc88e18 --- /dev/null +++ b/structures/channels/BaseChannel.ts @@ -0,0 +1,51 @@ +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 { textBasedChannels } from "./TextChannel.ts"; + +export abstract class BaseChannel implements Model { + constructor(session: Session, data: DiscordChannel) { + this.id = data.id; + this.session = session; + this.name = data.name; + this.type = data.type; + } + readonly id: Snowflake; + readonly session: Session; + + name?: string; + type: ChannelTypes; + + isText(): this is TextChannel { + return textBasedChannels.includes(this.type); + } + + isVoice(): this is VoiceChannel { + return this.type === ChannelTypes.GuildVoice; + } + + isDM(): this is DMChannel { + return this.type === ChannelTypes.DM; + } + + isNews(): this is NewsChannel { + return this.type === ChannelTypes.GuildNews; + } + + isThread(): this is ThreadChannel { + return this.type === ChannelTypes.GuildPublicThread || this.type === ChannelTypes.GuildPrivateThread; + } + + toString(): string { + return `<#${this.id}>`; + } +} + +export default BaseChannel; diff --git a/structures/channels/ChannelFactory.ts b/structures/channels/ChannelFactory.ts new file mode 100644 index 0000000..2603514 --- /dev/null +++ b/structures/channels/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/channels/DMChannel.ts similarity index 54% rename from structures/DMChannel.ts rename to structures/channels/DMChannel.ts index d13bd5d..ad94a05 100644 --- a/structures/DMChannel.ts +++ b/structures/channels/DMChannel.ts @@ -1,22 +1,23 @@ -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 { 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"; +import User from "../User.ts"; +import * as Routes from "../../util/Routes.ts"; export class DMChannel extends BaseChannel implements Model { constructor(session: Session, data: DiscordChannel) { super(session, data); - this.user = new User(this.session, data.recipents!.find((r) => r.id !== this.session.botId)!); - + this.type = data.type as ChannelTypes.DM | ChannelTypes.GroupDm; if (data.last_message_id) { this.lastMessageId = data.last_message_id; } } + override type: ChannelTypes.DM | ChannelTypes.GroupDm; user: User; lastMessageId?: Snowflake; @@ -31,4 +32,8 @@ export class DMChannel extends BaseChannel implements Model { } } +TextChannel.applyTo(DMChannel); + +export interface DMChannel extends Omit, Omit {} + export default DMChannel; diff --git a/structures/channels/GuildChannel.ts b/structures/channels/GuildChannel.ts new file mode 100644 index 0000000..aaa4e53 --- /dev/null +++ b/structures/channels/GuildChannel.ts @@ -0,0 +1,70 @@ +import type { Model } from "../Base.ts"; +import type { Snowflake } from "../../util/Snowflake.ts"; +import type { Session } from "../../session/Session.ts"; +import type { ChannelTypes, DiscordChannel, DiscordInviteMetadata } from "../../vendor/external.ts"; +import BaseChannel from "./BaseChannel.ts"; +import Invite from "../Invite.ts"; +import * as Routes from "../../util/Routes.ts"; + +/** + * Represent the options object to create a Thread Channel + * @link https://discord.com/developers/docs/resources/channel#start-thread-without-message + */ +export interface ThreadCreateOptions { + name: string; + autoArchiveDuration: 60 | 1440 | 4320 | 10080; + type: 10 | 11 | 12; + invitable?: boolean; + reason?: string; +} + +export class GuildChannel extends BaseChannel implements Model { + constructor(session: Session, data: DiscordChannel, guildId: Snowflake) { + super(session, data); + this.type = data.type as number; + this.guildId = guildId; + this.position = data.position; + data.topic ? this.topic = data.topic : null; + data.parent_id ? this.parentId = data.parent_id : undefined; + } + + override type: Exclude; + guildId: Snowflake; + topic?: string; + position?: number; + parentId?: Snowflake; + + async fetchInvites(): Promise { + const invites = await this.session.rest.runMethod( + this.session.rest, + "GET", + Routes.CHANNEL_INVITES(this.id), + ); + + 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, + "DELETE", + Routes.CHANNEL(this.id), + { + reason, + }, + ); + } +} + +export default GuildChannel; diff --git a/structures/NewsChannel.ts b/structures/channels/NewsChannel.ts similarity index 52% rename from structures/NewsChannel.ts rename to structures/channels/NewsChannel.ts index ef76049..a21ae61 100644 --- a/structures/NewsChannel.ts +++ b/structures/channels/NewsChannel.ts @@ -1,14 +1,18 @@ -import type { Snowflake } from "../util/Snowflake.ts"; -import type { Session } from "../session/Session.ts"; -import type { DiscordChannel } from "../vendor/external.ts"; +import type { Snowflake } from "../../util/Snowflake.ts"; +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"; -import Message from "./Message.ts"; -export class NewsChannel extends TextChannel { +export class NewsChannel extends GuildChannel { constructor(session: Session, data: DiscordChannel, guildId: Snowflake) { super(session, data, guildId); + this.type = data.type as ChannelTypes.GuildNews; this.defaultAutoArchiveDuration = data.default_auto_archive_duration; } + + override type: ChannelTypes.GuildNews; defaultAutoArchiveDuration?: number; crosspostMessage(messageId: Snowflake): Promise { @@ -20,4 +24,8 @@ export class NewsChannel extends TextChannel { } } +TextChannel.applyTo(NewsChannel); + +export interface NewsChannel extends TextChannel, GuildChannel {} + export default NewsChannel; diff --git a/structures/TextChannel.ts b/structures/channels/TextChannel.ts similarity index 58% rename from structures/TextChannel.ts rename to structures/channels/TextChannel.ts index 7ecd39b..6f5e324 100644 --- a/structures/TextChannel.ts +++ b/structures/channels/TextChannel.ts @@ -1,13 +1,21 @@ -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 { CreateMessage, EditMessage, ReactionResolvable } from "./Message.ts"; -import GuildChannel from "./GuildChannel.ts"; -import ThreadChannel from "./ThreadChannel.ts"; -import Message from "./Message.ts"; -import Invite from "./Invite.ts"; -import * as Routes from "../util/Routes.ts"; +// 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, + DiscordWebhook, + TargetTypes, +} from "../../vendor/external.ts"; +import type { CreateMessage, EditMessage, ReactionResolvable } from "../Message.ts"; +import { ChannelTypes } from "../../vendor/external.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"; /** * Represents the options object to create an invitation @@ -24,32 +32,76 @@ export interface DiscordInviteOptions { targetApplicationId?: Snowflake; } -/** - * Represent the options object to create a Thread Channel - * @link https://discord.com/developers/docs/resources/channel#start-thread-without-message - */ -export interface ThreadCreateOptions { +export interface CreateWebhook { name: string; - autoArchiveDuration: 60 | 1440 | 4320 | 10080; - type: 10 | 11 | 12; - invitable?: boolean; + avatar?: string; reason?: string; } -export class TextChannel extends GuildChannel { - constructor(session: Session, data: DiscordChannel, guildId: Snowflake) { - super(session, data, guildId); - data.last_message_id ? this.lastMessageId = data.last_message_id : undefined; - data.last_pin_timestamp ? this.lastPinTimestamp = data.last_pin_timestamp : undefined; +export const textBasedChannels = [ + ChannelTypes.DM, + ChannelTypes.GroupDm, + ChannelTypes.GuildPrivateThread, + ChannelTypes.GuildPublicThread, + ChannelTypes.GuildNews, + ChannelTypes.GuildText, +]; + +export type TextBasedChannels = + | ChannelTypes.DM + | ChannelTypes.GroupDm + | ChannelTypes.GuildPrivateThread + | ChannelTypes.GuildPublicThread + | ChannelTypes.GuildNews + | ChannelTypes.GuildText; + +export class TextChannel { + constructor(session: Session, data: DiscordChannel) { + 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; + + if (data.last_message_id) { + this.lastMessageId = data.last_message_id; + } + + if (data.last_pin_timestamp) { + this.lastPinTimestamp = data.last_pin_timestamp; + } } + 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, @@ -80,16 +132,6 @@ export class TextChannel extends GuildChannel { return 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 fetchMessages(options?: GetMessagesOptions): Promise { if (options?.limit! > 100) throw Error("Values must be between 0-100"); const messages = await this.session.rest.runMethod( @@ -159,6 +201,21 @@ export class TextChannel extends GuildChannel { 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/channels/ThreadChannel.ts similarity index 60% rename from structures/ThreadChannel.ts rename to structures/channels/ThreadChannel.ts index b846f93..459ba6e 100644 --- a/structures/ThreadChannel.ts +++ b/structures/channels/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 { Model } from "../Base.ts"; +import type { Snowflake } from "../../util/Snowflake.ts"; +import type { Session } from "../../session/Session.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/VoiceChannel.ts b/structures/channels/VoiceChannel.ts similarity index 82% rename from structures/VoiceChannel.ts rename to structures/channels/VoiceChannel.ts index fa26b7e..00da784 100644 --- a/structures/VoiceChannel.ts +++ b/structures/channels/VoiceChannel.ts @@ -1,8 +1,8 @@ -import type { Snowflake } from "../util/Snowflake.ts"; -import type { Session } from "../session/Session.ts"; -import type { DiscordChannel, VideoQualityModes } from "../vendor/external.ts"; -import { GatewayOpcodes } from "../vendor/external.ts"; -import { calculateShardId } from "../vendor/gateway/calculateShardId.ts"; +import type { Snowflake } from "../../util/Snowflake.ts"; +import type { Session } from "../../session/Session.ts"; +import type { DiscordChannel, VideoQualityModes } from "../../vendor/external.ts"; +import { GatewayOpcodes } from "../../vendor/external.ts"; +import { calculateShardId } from "../../vendor/gateway/calculateShardId.ts"; import GuildChannel from "./GuildChannel.ts"; /** diff --git a/structures/components/ActionRowComponent.ts b/structures/components/ActionRowComponent.ts new file mode 100644 index 0000000..8825553 --- /dev/null +++ b/structures/components/ActionRowComponent.ts @@ -0,0 +1,39 @@ +import type { Session } from "../../session/Session.ts"; +import type { DiscordComponent } from "../../vendor/external.ts"; +import type { ActionRowComponent, Component } from "./Component.ts"; +import { ButtonStyles, MessageComponentTypes } from "../../vendor/external.ts"; +import BaseComponent from "./Component.ts"; +import Button from "./ButtonComponent.ts"; +import LinkButton from "./LinkButtonComponent.ts"; +import SelectMenu from "./SelectMenuComponent.ts"; +import InputText from "./TextInputComponent.ts"; + +export class ActionRow extends BaseComponent implements ActionRowComponent { + constructor(session: Session, data: DiscordComponent) { + super(data.type); + + this.session = session; + this.type = data.type as MessageComponentTypes.ActionRow; + this.components = data.components!.map((component) => { + switch (component.type) { + 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 InputText(session, component); + case MessageComponentTypes.ActionRow: + throw new Error("Cannot have an action row inside an action row"); + } + }); + } + + readonly session: Session; + override type: MessageComponentTypes.ActionRow; + components: Array>; +} + +export default ActionRow; diff --git a/structures/components/ButtonComponent.ts b/structures/components/ButtonComponent.ts new file mode 100644 index 0000000..903e6ec --- /dev/null +++ b/structures/components/ButtonComponent.ts @@ -0,0 +1,33 @@ +import type { Session } from "../../session/Session.ts"; +import type { ButtonStyles, DiscordComponent } from "../../vendor/external.ts"; +import type { ButtonComponent } from "./Component.ts"; +import { MessageComponentTypes } from "../../vendor/external.ts"; +import BaseComponent from "./Component.ts"; +import Emoji from "../Emoji.ts"; + +export class Button extends BaseComponent implements ButtonComponent { + constructor(session: Session, data: DiscordComponent) { + super(data.type); + + this.session = session; + this.type = data.type as MessageComponentTypes.Button; + this.customId = data.custom_id; + this.label = data.label; + this.style = data.style as number; + this.disabled = data.disabled; + + if (data.emoji) { + this.emoji = new Emoji(session, data.emoji); + } + } + + readonly session: Session; + override type: MessageComponentTypes.Button; + customId?: string; + label?: string; + style: ButtonStyles.Primary | ButtonStyles.Secondary | ButtonStyles.Success | ButtonStyles.Danger; + disabled?: boolean; + emoji?: Emoji; +} + +export default Button; diff --git a/structures/components/Component.ts b/structures/components/Component.ts new file mode 100644 index 0000000..74e70d9 --- /dev/null +++ b/structures/components/Component.ts @@ -0,0 +1,93 @@ +import type Emoji from "../Emoji.ts"; +import { ButtonStyles, MessageComponentTypes, TextStyles } from "../../vendor/external.ts"; + +export class BaseComponent { + constructor(type: MessageComponentTypes) { + this.type = type; + } + + type: MessageComponentTypes; + + isActionRow(): this is ActionRowComponent { + return this.type === MessageComponentTypes.ActionRow; + } + + isButton(): this is ButtonComponent { + return this.type === MessageComponentTypes.Button; + } + + isSelectMenu(): this is SelectMenuComponent { + return this.type === MessageComponentTypes.SelectMenu; + } + + isTextInput(): this is TextInputComponent { + return this.type === MessageComponentTypes.InputText; + } +} + +/** Action Row Component */ +export interface ActionRowComponent { + type: MessageComponentTypes.ActionRow; + components: Array>; +} + +/** All Components */ +export type Component = + | ActionRowComponent + | ButtonComponent + | LinkButtonComponent + | SelectMenuComponent + | TextInputComponent; + +/** Button Component */ +export interface ButtonComponent { + type: MessageComponentTypes.Button; + style: ButtonStyles.Primary | ButtonStyles.Secondary | ButtonStyles.Success | ButtonStyles.Danger; + label?: string; + emoji?: Emoji; + customId?: string; + disabled?: boolean; +} + +/** Link Button Component */ +export interface LinkButtonComponent { + type: MessageComponentTypes.Button; + style: ButtonStyles.Link; + label?: string; + url: string; + disabled?: boolean; +} + +/** Select Menu Component */ +export interface SelectMenuComponent { + type: MessageComponentTypes.SelectMenu; + customId: string; + options: SelectMenuOption[]; + placeholder?: string; + minValue?: number; + maxValue?: number; + disabled?: boolean; +} + +/** Text Input Component */ +export interface TextInputComponent { + type: MessageComponentTypes.InputText; + customId: string; + style: TextStyles; + label: string; + minLength?: number; + maxLength?: number; + required?: boolean; + value?: string; + placeholder?: string; +} + +export interface SelectMenuOption { + label: string; + value: string; + description?: string; + emoji?: Emoji; + default?: boolean; +} + +export default BaseComponent; diff --git a/structures/components/ComponentFactory.ts b/structures/components/ComponentFactory.ts new file mode 100644 index 0000000..2a5d60a --- /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 { ButtonStyles, MessageComponentTypes } 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; diff --git a/structures/components/LinkButtonComponent.ts b/structures/components/LinkButtonComponent.ts new file mode 100644 index 0000000..ad3e53b --- /dev/null +++ b/structures/components/LinkButtonComponent.ts @@ -0,0 +1,33 @@ +import type { Session } from "../../session/Session.ts"; +import type { ButtonStyles, DiscordComponent } from "../../vendor/external.ts"; +import type { LinkButtonComponent } from "./Component.ts"; +import { MessageComponentTypes } from "../../vendor/external.ts"; +import BaseComponent from "./Component.ts"; +import Emoji from "../Emoji.ts"; + +export class LinkButton extends BaseComponent implements LinkButtonComponent { + constructor(session: Session, data: DiscordComponent) { + super(data.type); + + this.session = session; + this.type = data.type as MessageComponentTypes.Button; + this.url = data.url!; + this.label = data.label; + this.style = data.style as number; + this.disabled = data.disabled; + + if (data.emoji) { + this.emoji = new Emoji(session, data.emoji); + } + } + + readonly session: Session; + override type: MessageComponentTypes.Button; + url: string; + label?: string; + style: ButtonStyles.Link; + disabled?: boolean; + emoji?: Emoji; +} + +export default LinkButton; diff --git a/structures/components/SelectMenuComponent.ts b/structures/components/SelectMenuComponent.ts new file mode 100644 index 0000000..846104b --- /dev/null +++ b/structures/components/SelectMenuComponent.ts @@ -0,0 +1,39 @@ +import type { Session } from "../../session/Session.ts"; +import type { DiscordComponent } from "../../vendor/external.ts"; +import type { SelectMenuComponent, SelectMenuOption } from "./Component.ts"; +import { MessageComponentTypes } from "../../vendor/external.ts"; +import BaseComponent from "./Component.ts"; +import Emoji from "../Emoji.ts"; + +export class SelectMenu extends BaseComponent implements SelectMenuComponent { + constructor(session: Session, data: DiscordComponent) { + super(data.type); + + this.session = session; + this.type = data.type as MessageComponentTypes.SelectMenu; + this.customId = data.custom_id!; + this.options = data.options!.map((option) => { + return { + label: option.label, + description: option.description, + emoji: option.emoji || new Emoji(session, option.emoji!), + value: option.value, + }; + }); + this.placeholder = data.placeholder; + this.minValues = data.min_values; + this.maxValues = data.max_values; + this.disabled = data.disabled; + } + + readonly session: Session; + override type: MessageComponentTypes.SelectMenu; + customId: string; + options: SelectMenuOption[]; + placeholder?: string; + minValues?: number; + maxValues?: number; + disabled?: boolean; +} + +export default SelectMenu; diff --git a/structures/components/TextInputComponent.ts b/structures/components/TextInputComponent.ts new file mode 100644 index 0000000..27abfa3 --- /dev/null +++ b/structures/components/TextInputComponent.ts @@ -0,0 +1,38 @@ +import type { Session } from "../../session/Session.ts"; +import type { DiscordComponent } from "../../vendor/external.ts"; +import type { TextInputComponent } from "./Component.ts"; +import { MessageComponentTypes, TextStyles } from "../../vendor/external.ts"; +import BaseComponent from "./Component.ts"; + +export class TextInput extends BaseComponent implements TextInputComponent { + constructor(session: Session, data: DiscordComponent) { + super(data.type); + + this.session = session; + this.type = data.type as MessageComponentTypes.InputText; + this.customId = data.custom_id!; + this.label = data.label!; + this.style = data.style as TextStyles; + + this.placeholder = data.placeholder; + this.value = data.value; + + // @ts-ignore: vendor bug + this.minLength = data.min_length; + + // @ts-ignore: vendor bug + this.maxLength = data.max_length; + } + + readonly session: Session; + override type: MessageComponentTypes.InputText; + style: TextStyles; + customId: string; + label: string; + placeholder?: string; + value?: string; + minLength?: number; + maxLength?: number; +} + +export default TextInput; diff --git a/structures/AnonymousGuild.ts b/structures/guilds/AnonymousGuild.ts similarity index 82% rename from structures/AnonymousGuild.ts rename to structures/guilds/AnonymousGuild.ts index fa86532..c53308e 100644 --- a/structures/AnonymousGuild.ts +++ b/structures/guilds/AnonymousGuild.ts @@ -1,11 +1,11 @@ -import type { Model } from "./Base.ts"; -import type { Session } from "../session/Session.ts"; -import type { DiscordGuild, GuildNsfwLevel, VerificationLevels } from "../vendor/external.ts"; -import type { ImageFormat, ImageSize } from "../util/shared/images.ts"; -import { iconBigintToHash, iconHashToBigInt } from "../util/hash.ts"; -import { formatImageUrl } from "../util/shared/images.ts"; +import type { Model } from "../Base.ts"; +import type { Session } from "../../session/Session.ts"; +import type { DiscordGuild, GuildNsfwLevel, VerificationLevels } from "../../vendor/external.ts"; +import type { ImageFormat, ImageSize } from "../../util/shared/images.ts"; +import { iconBigintToHash, iconHashToBigInt } from "../../util/hash.ts"; +import { formatImageUrl } from "../../util/shared/images.ts"; import BaseGuild from "./BaseGuild.ts"; -import * as Routes from "../util/Routes.ts"; +import * as Routes from "../../util/Routes.ts"; export class AnonymousGuild extends BaseGuild implements Model { constructor(session: Session, data: Partial); // TODO: Improve this type (name and id are required) diff --git a/structures/BaseGuild.ts b/structures/guilds/BaseGuild.ts similarity index 72% rename from structures/BaseGuild.ts rename to structures/guilds/BaseGuild.ts index d233e9c..0b50c61 100644 --- a/structures/BaseGuild.ts +++ b/structures/guilds/BaseGuild.ts @@ -1,8 +1,8 @@ -import type { Model } from "./Base.ts"; -import type { Session } from "../session/Session.ts"; -import type { DiscordGuild, GuildFeatures } from "../vendor/external.ts"; -import { Snowflake } from "../util/Snowflake.ts"; -import { iconHashToBigInt } from "../util/hash.ts"; +import type { Model } from "../Base.ts"; +import type { Session } from "../../session/Session.ts"; +import type { DiscordGuild, GuildFeatures } from "../../vendor/external.ts"; +import { Snowflake } from "../../util/Snowflake.ts"; +import { iconHashToBigInt } from "../../util/hash.ts"; /** * Class for {@link Guild} and {@link AnonymousGuild} diff --git a/structures/Guild.ts b/structures/guilds/Guild.ts similarity index 94% rename from structures/Guild.ts rename to structures/guilds/Guild.ts index 1013814..fe39151 100644 --- a/structures/Guild.ts +++ b/structures/guilds/Guild.ts @@ -1,27 +1,27 @@ -import type { Model } from "./Base.ts"; -import type { Snowflake } from "../util/Snowflake.ts"; -import type { Session } from "../session/Session.ts"; +import type { Model } from "../Base.ts"; +import type { Snowflake } from "../../util/Snowflake.ts"; +import type { Session } from "../../session/Session.ts"; import type { DiscordEmoji, DiscordGuild, DiscordInviteMetadata, DiscordMemberWithUser, DiscordRole, -} from "../vendor/external.ts"; -import type { GetInvite } from "../util/Routes.ts"; +} from "../../vendor/external.ts"; +import type { GetInvite } from "../../util/Routes.ts"; import { DefaultMessageNotificationLevels, ExplicitContentFilterLevels, VerificationLevels, -} from "../vendor/external.ts"; -import { iconBigintToHash, iconHashToBigInt } from "../util/hash.ts"; -import { urlToBase64 } from "../util/urlToBase64.ts"; -import Member from "./Member.ts"; +} from "../../vendor/external.ts"; +import { iconBigintToHash, iconHashToBigInt } from "../../util/hash.ts"; +import { urlToBase64 } from "../../util/urlToBase64.ts"; +import Member from "../Member.ts"; import BaseGuild from "./BaseGuild.ts"; -import Role from "./Role.ts"; -import GuildEmoji from "./GuildEmoji.ts"; -import Invite from "./Invite.ts"; -import * as Routes from "../util/Routes.ts"; +import Role from "../Role.ts"; +import GuildEmoji from "../GuildEmoji.ts"; +import Invite from "../Invite.ts"; +import * as Routes from "../../util/Routes.ts"; export interface CreateRole { name?: string; @@ -82,8 +82,8 @@ export interface BeginGuildPrune { } export interface ModifyRolePositions { - id: Snowflake; - position?: number | null; + id: Snowflake; + position?: number | null; } /** @@ -217,8 +217,6 @@ export class Guild extends BaseGuild implements Model { return new Role(this.session, role, this.id); } - - async addRole(memberId: Snowflake, roleId: Snowflake, { reason }: { reason?: string } = {}) { await this.session.rest.runMethod( this.session.rest, @@ -239,7 +237,7 @@ export class Guild extends BaseGuild implements Model { /** * Returns the roles moved - * */ + */ async moveRoles(options: ModifyRolePositions[]) { const roles = await this.session.rest.runMethod( this.session.rest, diff --git a/structures/InviteGuild.ts b/structures/guilds/InviteGuild.ts similarity index 66% rename from structures/InviteGuild.ts rename to structures/guilds/InviteGuild.ts index 0465165..85e3d50 100644 --- a/structures/InviteGuild.ts +++ b/structures/guilds/InviteGuild.ts @@ -1,8 +1,8 @@ -import type { Model } from "./Base.ts"; -import type { Session } from "../session/Session.ts"; -import type { DiscordGuild } from "../vendor/external.ts"; +import type { Model } from "../Base.ts"; +import type { Session } from "../../session/Session.ts"; +import type { DiscordGuild } from "../../vendor/external.ts"; import AnonymousGuild from "./AnonymousGuild.ts"; -import WelcomeScreen from "./WelcomeScreen.ts"; +import WelcomeScreen from "../WelcomeScreen.ts"; export class InviteGuild extends AnonymousGuild implements Model { constructor(session: Session, data: Partial) { diff --git a/structures/Interaction.ts b/structures/interactions/Interaction.ts similarity index 88% rename from structures/Interaction.ts rename to structures/interactions/Interaction.ts index f11dc99..5ed2b94 100644 --- a/structures/Interaction.ts +++ b/structures/interactions/Interaction.ts @@ -1,19 +1,19 @@ -import type { Model } from "./Base.ts"; -import type { Snowflake } from "../util/Snowflake.ts"; -import type { Session } from "../session/Session.ts"; +import type { Model } from "../Base.ts"; +import type { Snowflake } from "../../util/Snowflake.ts"; +import type { Session } from "../../session/Session.ts"; import type { DiscordInteraction, DiscordMessage, FileContent, InteractionResponseTypes, InteractionTypes, -} from "../vendor/external.ts"; -import type { MessageFlags } from "../util/shared/flags.ts"; -import type { AllowedMentions } from "./Message.ts"; -import User from "./User.ts"; -import Message from "./Message.ts"; -import Member from "./Member.ts"; -import * as Routes from "../util/Routes.ts"; +} from "../../vendor/external.ts"; +import type { MessageFlags } from "../../util/shared/flags.ts"; +import type { AllowedMentions } from "../Message.ts"; +import User from "../User.ts"; +import Message from "../Message.ts"; +import Member from "../Member.ts"; +import * as Routes from "../../util/Routes.ts"; export interface InteractionResponse { type: InteractionResponseTypes; @@ -38,6 +38,8 @@ export interface ApplicationCommandOptionChoice { value: string | number; } +// TODO: abstract Interaction, CommandInteraction, ComponentInteraction, PingInteraction, etc + export class Interaction implements Model { constructor(session: Session, data: DiscordInteraction) { this.session = session; diff --git a/tests/mod.ts b/tests/mod.ts index f5e7a96..03fd262 100644 --- a/tests/mod.ts +++ b/tests/mod.ts @@ -1,19 +1,26 @@ +import "https://deno.land/std@0.146.0/dotenv/load.ts"; import { GatewayIntents, Session } from "./deps.ts"; -if (!Deno.args[0]) { +const token = Deno.env.get("TOKEN") ?? Deno.args[0]; + +if (!token) { throw new Error("Please provide a token"); } const intents = GatewayIntents.MessageContent | GatewayIntents.Guilds | GatewayIntents.GuildMessages; -const session = new Session({ token: Deno.args[0], intents }); +const session = new Session({ token, intents }); session.on("ready", (payload) => { console.log("Logged in as:", payload.user.username); }); -const PREFIX = "&"; +const PREFIX = ">"; session.on("messageCreate", (message) => { + if (message.author.bot) { + return; + } + const args = message.content.slice(PREFIX.length).trim().split(/\s+/gm); const name = args.shift()?.toLowerCase(); diff --git a/util/Routes.ts b/util/Routes.ts index db2654e..247cadd 100644 --- a/util/Routes.ts +++ b/util/Routes.ts @@ -224,3 +224,7 @@ export function CHANNEL_MESSAGE_CROSSPOST(channelId: Snowflake, messageId: Snowf export function GUILD_MEMBER_ROLE(guildId: Snowflake, memberId: Snowflake, roleId: Snowflake) { return `/guilds/${guildId}/members/${memberId}/roles/${roleId}`; } + +export function CHANNEL_WEBHOOKS(channelId: Snowflake) { + return `/channels/${channelId}/webhooks`; +}