refactor: structures & folders

This commit is contained in:
Yuzu 2022-07-02 12:15:47 -05:00
parent e66356f2c4
commit d1d0a1ce76
19 changed files with 29 additions and 1193 deletions

View File

@ -15,14 +15,14 @@ import type {
} from "../vendor/external.ts"; } from "../vendor/external.ts";
import type { Snowflake } from "../util/Snowflake.ts"; import type { Snowflake } from "../util/Snowflake.ts";
import type { Session } from "../session/Session.ts"; import type { Session } from "../session/Session.ts";
import type { Channel } from "../structures/BaseChannel.ts"; import type { Channel } from "../structures/channels/ChannelFactory.ts";
import ChannelFactory from "../structures/ChannelFactory.ts"; import ChannelFactory from "../structures/channels/ChannelFactory.ts";
import GuildChannel from "../structures/GuildChannel.ts"; import GuildChannel from "../structures/channels/GuildChannel.ts";
import ThreadChannel from "../structures/ThreadChannel.ts"; import ThreadChannel from "../structures/channels/ThreadChannel.ts";
import Member from "../structures/Member.ts"; import Member from "../structures/Member.ts";
import Message from "../structures/Message.ts"; import Message from "../structures/Message.ts";
import User from "../structures/User.ts"; import User from "../structures/User.ts";
import Interaction from "../structures/Interaction.ts"; import Interaction from "../structures/interactions/Interaction.ts";
export type RawHandler<T> = (...args: [Session, number, T]) => void; export type RawHandler<T> = (...args: [Session, number, T]) => void;
export type Handler<T extends unknown[]> = (...args: T) => unknown; export type Handler<T extends unknown[]> = (...args: T) => unknown;
@ -103,8 +103,8 @@ export const THREAD_LIST_SYNC: RawHandler<DiscordThreadListSync> = (session, _sh
guildId: payload.guild_id, guildId: payload.guild_id,
channelIds: payload.channel_ids ?? [], channelIds: payload.channel_ids ?? [],
threads: payload.threads.map((channel) => new ThreadChannel(session, channel, payload.guild_id)), threads: payload.threads.map((channel) => new ThreadChannel(session, channel, payload.guild_id)),
// @ts-ignore: TODO: thread member structure
members: payload.members.map((member) => members: payload.members.map((member) =>
// @ts-ignore: TODO: thread member structure
new Member(session, member as DiscordMemberWithUser, payload.guild_id) new Member(session, member as DiscordMemberWithUser, payload.guild_id)
), ),
}); });

28
mod.ts
View File

@ -1,29 +1,26 @@
export * from "./structures/AnonymousGuild.ts";
export * from "./structures/Attachment.ts"; export * from "./structures/Attachment.ts";
export * from "./structures/Base.ts"; export * from "./structures/Base.ts";
export * from "./structures/BaseGuild.ts";
export * from "./structures/BaseChannel.ts";
export * from "./structures/DMChannel.ts";
export * from "./structures/Embed.ts"; export * from "./structures/Embed.ts";
export * from "./structures/Emoji.ts"; export * from "./structures/Emoji.ts";
export * from "./structures/Guild.ts";
export * from "./structures/GuildChannel.ts";
export * from "./structures/GuildEmoji.ts"; export * from "./structures/GuildEmoji.ts";
export * from "./structures/Interaction.ts";
export * from "./structures/Invite.ts"; export * from "./structures/Invite.ts";
export * from "./structures/InviteGuild.ts";
export * from "./structures/Member.ts"; export * from "./structures/Member.ts";
export * from "./structures/Message.ts"; export * from "./structures/Message.ts";
export * from "./structures/NewsChannel.ts";
export * from "./structures/Permissions.ts"; export * from "./structures/Permissions.ts";
export * from "./structures/Role.ts"; export * from "./structures/Role.ts";
export * from "./structures/TextChannel.ts";
export * from "./structures/ThreadChannel.ts";
export * from "./structures/User.ts"; export * from "./structures/User.ts";
export * from "./structures/VoiceChannel.ts";
export * from "./structures/WelcomeChannel.ts"; export * from "./structures/WelcomeChannel.ts";
export * from "./structures/WelcomeScreen.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/ActionRowComponent.ts";
export * from "./structures/components/ButtonComponent.ts"; export * from "./structures/components/ButtonComponent.ts";
export * from "./structures/components/Component.ts"; export * from "./structures/components/Component.ts";
@ -31,6 +28,13 @@ export * from "./structures/components/LinkButtonComponent.ts";
export * from "./structures/components/SelectMenuComponent.ts"; export * from "./structures/components/SelectMenuComponent.ts";
export * from "./structures/components/TextInputComponent.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 "./session/Session.ts";
export * from "./util/shared/flags.ts"; export * from "./util/shared/flags.ts";

View File

@ -1,55 +0,0 @@
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";
export class AnonymousGuild extends BaseGuild implements Model {
constructor(session: Session, data: Partial<DiscordGuild>); // TODO: Improve this type (name and id are required)
constructor(session: Session, data: DiscordGuild) {
super(session, data);
this.splashHash = data.splash ? iconHashToBigInt(data.splash) : undefined;
this.bannerHash = data.banner ? iconHashToBigInt(data.banner) : undefined;
this.verificationLevel = data.verification_level;
this.vanityUrlCode = data.vanity_url_code ? data.vanity_url_code : undefined;
this.nsfwLevel = data.nsfw_level;
this.description = data.description ? data.description : undefined;
this.premiumSubscriptionCount = data.premium_subscription_count;
}
splashHash?: bigint;
bannerHash?: bigint;
verificationLevel: VerificationLevels;
vanityUrlCode?: string;
nsfwLevel: GuildNsfwLevel;
description?: string;
premiumSubscriptionCount?: number;
splashUrl(options: { size?: ImageSize; format?: ImageFormat } = { size: 128 }) {
if (this.splashHash) {
return formatImageUrl(
Routes.GUILD_SPLASH(this.id, iconBigintToHash(this.splashHash)),
options.size,
options.format,
);
}
}
bannerUrl(options: { size?: ImageSize; format?: ImageFormat } = { size: 128 }) {
if (this.bannerHash) {
return formatImageUrl(
Routes.GUILD_BANNER(this.id, iconBigintToHash(this.bannerHash)),
options.size,
options.format,
);
}
}
}
export default AnonymousGuild;

View File

@ -1,51 +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 } 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;

View File

@ -1,41 +0,0 @@
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}
*/
export abstract class BaseGuild implements Model {
constructor(session: Session, data: DiscordGuild) {
this.session = session;
this.id = data.id;
this.name = data.name;
this.iconHash = data.icon ? iconHashToBigInt(data.icon) : undefined;
this.features = data.features;
}
readonly session: Session;
readonly id: Snowflake;
name: string;
iconHash?: bigint;
features: GuildFeatures[];
get createdTimestamp() {
return Snowflake.snowflakeToTimestamp(this.id);
}
get createdAt() {
return new Date(this.createdTimestamp);
}
toString() {
return this.name;
}
}
export default BaseGuild;

View File

@ -1,39 +0,0 @@
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;

View File

@ -1,40 +0,0 @@
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 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;
async close() {
const channel = await this.session.rest.runMethod<DiscordChannel>(
this.session.rest,
"DELETE",
Routes.CHANNEL(this.id),
);
return new DMChannel(this.session, channel);
}
}
TextChannel.applyTo(DMChannel);
export interface DMChannel extends TextChannel, BaseChannel {}
export default DMChannel;

View File

@ -1,367 +0,0 @@
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";
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";
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";
export interface CreateRole {
name?: string;
color?: number;
iconHash?: string | bigint;
unicodeEmoji?: string;
hoist?: boolean;
mentionable?: boolean;
}
export interface ModifyGuildRole {
name?: string;
color?: number;
hoist?: boolean;
mentionable?: boolean;
unicodeEmoji?: string;
}
export interface CreateGuildEmoji {
name: string;
image: string;
roles?: Snowflake[];
reason?: string;
}
export interface ModifyGuildEmoji {
name?: string;
roles?: Snowflake[];
}
/**
* @link https://discord.com/developers/docs/resources/guild#create-guild-ban
*/
export interface CreateGuildBan {
deleteMessageDays?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
reason?: string;
}
/**
* @link https://discord.com/developers/docs/resources/guild#modify-guild-member
*/
export interface ModifyGuildMember {
nick?: string;
roles?: Snowflake[];
mute?: boolean;
deaf?: boolean;
channelId?: Snowflake;
communicationDisabledUntil?: number;
}
/**
* @link https://discord.com/developers/docs/resources/guild#begin-guild-prune
*/
export interface BeginGuildPrune {
days?: number;
computePruneCount?: boolean;
includeRoles?: Snowflake[];
}
export interface ModifyRolePositions {
id: Snowflake;
position?: number | null;
}
/**
* Represents a guild
* @link https://discord.com/developers/docs/resources/guild#guild-object
*/
export class Guild extends BaseGuild implements Model {
constructor(session: Session, data: DiscordGuild) {
super(session, data);
this.splashHash = data.splash ? iconHashToBigInt(data.splash) : undefined;
this.discoverySplashHash = data.discovery_splash ? iconHashToBigInt(data.discovery_splash) : undefined;
this.ownerId = data.owner_id;
this.widgetEnabled = !!data.widget_enabled;
this.widgetChannelId = data.widget_channel_id ? data.widget_channel_id : undefined;
this.vefificationLevel = data.verification_level;
this.defaultMessageNotificationLevel = data.default_message_notifications;
this.explicitContentFilterLevel = data.explicit_content_filter;
this.members = data.members?.map((member) => new Member(session, { ...member, user: member.user! }, data.id)) ??
[];
this.roles = data.roles.map((role) => new Role(session, role, data.id));
this.emojis = data.emojis.map((guildEmoji) => new GuildEmoji(session, guildEmoji, data.id));
}
splashHash?: bigint;
discoverySplashHash?: bigint;
ownerId: Snowflake;
widgetEnabled: boolean;
widgetChannelId?: Snowflake;
vefificationLevel: VerificationLevels;
defaultMessageNotificationLevel: DefaultMessageNotificationLevels;
explicitContentFilterLevel: ExplicitContentFilterLevels;
members: Member[];
roles: Role[];
emojis: GuildEmoji[];
/**
* 'null' would reset the nickname
*/
async editBotNickname(options: { nick: string | null; reason?: string }) {
const result = await this.session.rest.runMethod<{ nick?: string } | undefined>(
this.session.rest,
"PATCH",
Routes.USER_NICK(this.id),
options,
);
return result?.nick;
}
async createEmoji(options: CreateGuildEmoji): Promise<GuildEmoji> {
if (options.image && !options.image.startsWith("data:image/")) {
options.image = await urlToBase64(options.image);
}
const emoji = await this.session.rest.runMethod<DiscordEmoji>(
this.session.rest,
"POST",
Routes.GUILD_EMOJIS(this.id),
options,
);
return new GuildEmoji(this.session, emoji, this.id);
}
async deleteEmoji(id: Snowflake, { reason }: { reason?: string } = {}): Promise<void> {
await this.session.rest.runMethod<undefined>(
this.session.rest,
"DELETE",
Routes.GUILD_EMOJI(this.id, id),
{ reason },
);
}
async editEmoji(id: Snowflake, options: ModifyGuildEmoji): Promise<GuildEmoji> {
const emoji = await this.session.rest.runMethod<DiscordEmoji>(
this.session.rest,
"PATCH",
Routes.GUILD_EMOJI(this.id, id),
options,
);
return new GuildEmoji(this.session, emoji, this.id);
}
async createRole(options: CreateRole): Promise<Role> {
let icon: string | undefined;
if (options.iconHash) {
if (typeof options.iconHash === "string") {
icon = options.iconHash;
} else {
icon = iconBigintToHash(options.iconHash);
}
}
const role = await this.session.rest.runMethod<DiscordRole>(
this.session.rest,
"PUT",
Routes.GUILD_ROLES(this.id),
{
name: options.name,
color: options.color,
icon,
unicode_emoji: options.unicodeEmoji,
hoist: options.hoist,
mentionable: options.mentionable,
},
);
return new Role(this.session, role, this.id);
}
async deleteRole(roleId: Snowflake): Promise<void> {
await this.session.rest.runMethod<undefined>(this.session.rest, "DELETE", Routes.GUILD_ROLE(this.id, roleId));
}
async editRole(roleId: Snowflake, options: ModifyGuildRole): Promise<Role> {
const role = await this.session.rest.runMethod<DiscordRole>(
this.session.rest,
"PATCH",
Routes.GUILD_ROLE(this.id, roleId),
{
name: options.name,
color: options.color,
hoist: options.hoist,
mentionable: options.mentionable,
},
);
return new Role(this.session, role, this.id);
}
async addRole(memberId: Snowflake, roleId: Snowflake, { reason }: { reason?: string } = {}) {
await this.session.rest.runMethod<undefined>(
this.session.rest,
"PUT",
Routes.GUILD_MEMBER_ROLE(this.id, memberId, roleId),
{ reason },
);
}
async removeRole(memberId: Snowflake, roleId: Snowflake, { reason }: { reason?: string } = {}) {
await this.session.rest.runMethod<undefined>(
this.session.rest,
"DELETE",
Routes.GUILD_MEMBER_ROLE(this.id, memberId, roleId),
{ reason },
);
}
/**
* Returns the roles moved
*/
async moveRoles(options: ModifyRolePositions[]) {
const roles = await this.session.rest.runMethod<DiscordRole[]>(
this.session.rest,
"PATCH",
Routes.GUILD_ROLES(this.id),
options,
);
return roles.map((role) => new Role(this.session, role, this.id));
}
async deleteInvite(inviteCode: string): Promise<void> {
await this.session.rest.runMethod<undefined>(
this.session.rest,
"DELETE",
Routes.INVITE(inviteCode),
{},
);
}
async fetchInvite(inviteCode: string, options: GetInvite): Promise<Invite> {
const inviteMetadata = await this.session.rest.runMethod<DiscordInviteMetadata>(
this.session.rest,
"GET",
Routes.INVITE(inviteCode, options),
);
return new Invite(this.session, inviteMetadata);
}
async fetchInvites(): Promise<Invite[]> {
const invites = await this.session.rest.runMethod<DiscordInviteMetadata[]>(
this.session.rest,
"GET",
Routes.GUILD_INVITES(this.id),
);
return invites.map((invite) => new Invite(this.session, invite));
}
/**
* Bans the member
*/
async banMember(memberId: Snowflake, options: CreateGuildBan) {
await this.session.rest.runMethod<undefined>(
this.session.rest,
"PUT",
Routes.GUILD_BAN(this.id, memberId),
options
? {
delete_message_days: options.deleteMessageDays,
reason: options.reason,
}
: {},
);
}
/**
* Kicks the member
*/
async kickMember(memberId: Snowflake, { reason }: { reason?: string }) {
await this.session.rest.runMethod<undefined>(
this.session.rest,
"DELETE",
Routes.GUILD_MEMBER(this.id, memberId),
{ reason },
);
}
/*
* Unbans the member
* */
async unbanMember(memberId: Snowflake) {
await this.session.rest.runMethod<undefined>(
this.session.rest,
"DELETE",
Routes.GUILD_BAN(this.id, memberId),
);
}
async editMember(memberId: Snowflake, options: ModifyGuildMember) {
const member = await this.session.rest.runMethod<DiscordMemberWithUser>(
this.session.rest,
"PATCH",
Routes.GUILD_MEMBER(this.id, memberId),
{
nick: options.nick,
roles: options.roles,
mute: options.mute,
deaf: options.deaf,
channel_id: options.channelId,
communication_disabled_until: options.communicationDisabledUntil
? new Date(options.communicationDisabledUntil).toISOString()
: undefined,
},
);
return new Member(this.session, member, this.id);
}
async pruneMembers(options: BeginGuildPrune): Promise<number> {
const result = await this.session.rest.runMethod<{ pruned: number }>(
this.session.rest,
"POST",
Routes.GUILD_PRUNE(this.id),
{
days: options.days,
compute_prune_count: options.computePruneCount,
include_roles: options.includeRoles,
},
);
return result.pruned;
}
async getPruneCount(): Promise<number> {
const result = await this.session.rest.runMethod<{ pruned: number }>(
this.session.rest,
"GET",
Routes.GUILD_PRUNE(this.id),
);
return result.pruned;
}
}
export default Guild;

View File

@ -1,70 +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, 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<ChannelTypes, ChannelTypes.DM | ChannelTypes.GroupDm>;
guildId: Snowflake;
topic?: string;
position?: number;
parentId?: Snowflake;
async fetchInvites(): Promise<Invite[]> {
const invites = await this.session.rest.runMethod<DiscordInviteMetadata[]>(
this.session.rest,
"GET",
Routes.CHANNEL_INVITES(this.id),
);
return invites.map((invite) => new Invite(this.session, invite));
}
/*
async createThread(options: ThreadCreateOptions): Promise<ThreadChannel> {
const thread = await this.session.rest.runMethod<DiscordChannel>(
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<DiscordChannel>(
this.session.rest,
"DELETE",
Routes.CHANNEL(this.id),
{
reason,
},
);
}
}
export default GuildChannel;

View File

@ -2,8 +2,8 @@ import type { Model } from "./Base.ts";
import type { Snowflake } from "../util/Snowflake.ts"; import type { Snowflake } from "../util/Snowflake.ts";
import type { Session } from "../session/Session.ts"; import type { Session } from "../session/Session.ts";
import type { DiscordEmoji } from "../vendor/external.ts"; import type { DiscordEmoji } from "../vendor/external.ts";
import type { ModifyGuildEmoji } from "./Guild.ts"; import type { ModifyGuildEmoji } from "./guilds/Guild.ts";
import Guild from "./Guild.ts"; import Guild from "./guilds/Guild.ts";
import Emoji from "./Emoji.ts"; import Emoji from "./Emoji.ts";
import User from "./User.ts"; import User from "./User.ts";
import * as Routes from "../util/Routes.ts"; import * as Routes from "../util/Routes.ts";

View File

@ -1,145 +0,0 @@
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";
export interface InteractionResponse {
type: InteractionResponseTypes;
data?: InteractionApplicationCommandCallbackData;
}
export interface InteractionApplicationCommandCallbackData {
content?: string;
tts?: boolean;
allowedMentions?: AllowedMentions;
files?: FileContent[];
customId?: string;
title?: string;
// components?: Component[];
flags?: MessageFlags;
choices?: ApplicationCommandOptionChoice[];
}
/** https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoptionchoice */
export interface ApplicationCommandOptionChoice {
name: string;
value: string | number;
}
// TODO: abstract Interaction, CommandInteraction, ComponentInteraction, PingInteraction, etc
export class Interaction implements Model {
constructor(session: Session, data: DiscordInteraction) {
this.session = session;
this.id = data.id;
this.token = data.token;
this.type = data.type;
this.guildId = data.guild_id;
this.channelId = data.channel_id;
this.applicationId = data.application_id;
this.locale = data.locale;
this.data = data.data;
if (!data.guild_id) {
this.user = new User(session, data.user!);
} else {
this.member = new Member(session, data.member!, data.guild_id);
}
}
readonly session: Session;
readonly id: Snowflake;
readonly token: string;
type: InteractionTypes;
guildId?: Snowflake;
channelId?: Snowflake;
applicationId?: Snowflake;
locale?: string;
// deno-lint-ignore no-explicit-any
data: any;
user?: User;
member?: Member;
async respond({ type, data }: InteractionResponse) {
const toSend = {
tts: data?.tts,
title: data?.title,
flags: data?.flags,
content: data?.content,
choices: data?.choices,
custom_id: data?.customId,
allowed_mentions: data?.allowedMentions
? {
users: data.allowedMentions.users,
roles: data.allowedMentions.roles,
parse: data.allowedMentions.parse,
replied_user: data.allowedMentions.repliedUser,
}
: { parse: [] },
};
if (this.session.unrepliedInteractions.delete(BigInt(this.id))) {
await this.session.rest.sendRequest<undefined>(
this.session.rest,
{
url: Routes.INTERACTION_ID_TOKEN(this.id, this.token),
method: "POST",
payload: this.session.rest.createRequestBody(this.session.rest, {
method: "POST",
body: {
type: type,
data: toSend,
file: data?.files,
},
headers: {
// remove authorization header
Authorization: "",
},
}),
},
);
return;
}
const result = await this.session.rest.sendRequest<DiscordMessage>(
this.session.rest,
{
url: Routes.WEBHOOK(this.session.applicationId ?? this.session.botId, this.token),
method: "POST",
payload: this.session.rest.createRequestBody(this.session.rest, {
method: "POST",
body: {
...toSend,
file: data?.files,
},
headers: {
// remove authorization header
Authorization: "",
},
}),
},
);
return new Message(this.session, result);
}
inGuild(): this is Interaction & { user: undefined; guildId: Snowflake; member: Member } {
return !!this.guildId;
}
}
export default Interaction;

View File

@ -1,9 +1,9 @@
import type { Session } from "../session/Session.ts"; import type { Session } from "../session/Session.ts";
import type { DiscordInvite } from "../vendor/external.ts"; import type { DiscordInvite } from "../vendor/external.ts";
import { TargetTypes } 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 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 * @link https://discord.com/developers/docs/resources/invite#invite-object

View File

@ -1,19 +0,0 @@
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";
export class InviteGuild extends AnonymousGuild implements Model {
constructor(session: Session, data: Partial<DiscordGuild>) {
super(session, data);
if (data.welcome_screen) {
this.welcomeScreen = new WelcomeScreen(session, data.welcome_screen);
}
}
welcomeScreen?: WelcomeScreen;
}
export default InviteGuild;

View File

@ -3,10 +3,10 @@ import type { Snowflake } from "../util/Snowflake.ts";
import type { Session } from "../session/Session.ts"; import type { Session } from "../session/Session.ts";
import type { DiscordMemberWithUser } from "../vendor/external.ts"; import type { DiscordMemberWithUser } from "../vendor/external.ts";
import type { ImageFormat, ImageSize } from "../util/shared/images.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 { iconBigintToHash, iconHashToBigInt } from "../util/hash.ts";
import User from "./User.ts"; import User from "./User.ts";
import Guild from "./Guild.ts"; import Guild from "./guilds/Guild.ts";
import * as Routes from "../util/Routes.ts"; import * as Routes from "../util/Routes.ts";
/** /**

View File

@ -1,31 +0,0 @@
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";
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<Message> {
return Message.prototype.crosspost.call({ id: messageId, channelId: this.id, session: this.session });
}
get publishMessage() {
return this.crosspostMessage;
}
}
TextChannel.applyTo(NewsChannel);
export interface NewsChannel extends TextChannel, GuildChannel {}
export default NewsChannel;

View File

@ -4,7 +4,7 @@ import type { Session } from "../session/Session.ts";
import { Snowflake } from "../util/Snowflake.ts"; import { Snowflake } from "../util/Snowflake.ts";
import { iconHashToBigInt } from "../util/hash.ts"; import { iconHashToBigInt } from "../util/hash.ts";
import Permissions from "./Permissions.ts"; import Permissions from "./Permissions.ts";
import Guild, { ModifyGuildRole } from "./Guild.ts"; import Guild, { ModifyGuildRole } from "./guilds/Guild.ts";
export class Role implements Model { export class Role implements Model {
constructor(session: Session, data: DiscordRole, guildId: Snowflake) { constructor(session: Session, data: DiscordRole, guildId: Snowflake) {

View File

@ -1,215 +0,0 @@
// 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
* @link https://discord.com/developers/docs/resources/channel#create-channel-invite-json-params
*/
export interface DiscordInviteOptions {
maxAge?: number;
maxUses?: number;
unique?: boolean;
temporary: boolean;
reason?: string;
targetType?: TargetTypes;
targetUserId?: Snowflake;
targetApplicationId?: Snowflake;
}
export interface CreateWebhook {
name: string;
avatar?: string;
reason?: string;
}
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<Message[] | []> {
const messages = await this.session.rest.runMethod<DiscordMessage[]>(
this.session.rest,
"GET",
Routes.CHANNEL_PINS(this.id),
);
return messages[0] ? messages.map((x: DiscordMessage) => new Message(this.session, x)) : [];
}
async createInvite(options?: DiscordInviteOptions) {
const invite = await this.session.rest.runMethod<DiscordInvite>(
this.session.rest,
"POST",
Routes.CHANNEL_INVITES(this.id),
options
? {
max_age: options.maxAge,
max_uses: options.maxUses,
temporary: options.temporary,
unique: options.unique,
target_type: options.targetType,
target_user_id: options.targetUserId,
target_application_id: options.targetApplicationId,
}
: {},
);
return new Invite(this.session, invite);
}
async fetchMessages(options?: GetMessagesOptions): Promise<Message[] | []> {
if (options?.limit! > 100) throw Error("Values must be between 0-100");
const messages = await this.session.rest.runMethod<DiscordMessage[]>(
this.session.rest,
"GET",
Routes.CHANNEL_MESSAGES(this.id, options),
);
return messages[0] ? messages.map((x) => new Message(this.session, x)) : [];
}
async sendTyping() {
await this.session.rest.runMethod<undefined>(
this.session.rest,
"POST",
Routes.CHANNEL_TYPING(this.id),
);
}
async pinMessage(messageId: Snowflake) {
await Message.prototype.pin.call({ id: messageId, channelId: this.id, session: this.session });
}
async unpinMessage(messageId: Snowflake) {
await Message.prototype.unpin.call({ id: messageId, channelId: this.id, session: this.session });
}
async addReaction(messageId: Snowflake, reaction: ReactionResolvable) {
await Message.prototype.addReaction.call(
{ channelId: this.id, id: messageId, session: this.session },
reaction,
);
}
async removeReaction(messageId: Snowflake, reaction: ReactionResolvable, options?: { userId: Snowflake }) {
await Message.prototype.removeReaction.call(
{ channelId: this.id, id: messageId, session: this.session },
reaction,
options,
);
}
async removeReactionEmoji(messageId: Snowflake, reaction: ReactionResolvable) {
await Message.prototype.removeReactionEmoji.call(
{ channelId: this.id, id: messageId, session: this.session },
reaction,
);
}
async nukeReactions(messageId: Snowflake) {
await Message.prototype.nukeReactions.call({ channelId: this.id, id: messageId });
}
async fetchReactions(messageId: Snowflake, reaction: ReactionResolvable, options?: GetReactions) {
const users = await Message.prototype.fetchReactions.call(
{ channelId: this.id, id: messageId, session: this.session },
reaction,
options,
);
return users;
}
sendMessage(options: CreateMessage) {
return Message.prototype.reply.call({ channelId: this.id, session: this.session }, options);
}
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<DiscordWebhook>(
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;

View File

@ -1,35 +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 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;
this.locked = !!data.thread_metadata?.locked;
this.messageCount = data.message_count;
this.memberCount = data.member_count;
this.ownerId = data.owner_id;
}
override type: ChannelTypes.GuildNewsThread | ChannelTypes.GuildPrivateThread | ChannelTypes.GuildPublicThread;
archived?: boolean;
archiveTimestamp?: string;
autoArchiveDuration?: number;
locked?: boolean;
messageCount?: number;
memberCount?: number;
ownerId?: Snowflake;
}
TextChannel.applyTo(ThreadChannel);
export interface ThreadChannel extends Omit<GuildChannel, "type">, Omit<TextChannel, "type"> {}
export default ThreadChannel;

View File

@ -1,60 +0,0 @@
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";
/**
* @link https://discord.com/developers/docs/topics/gateway#update-voice-state
*/
export interface UpdateVoiceState {
guildId: string;
channelId?: string;
selfMute: boolean;
selfDeaf: boolean;
}
export class VoiceChannel extends GuildChannel {
constructor(session: Session, data: DiscordChannel, guildId: Snowflake) {
super(session, data, guildId);
this.bitRate = data.bitrate;
this.userLimit = data.user_limit ?? 0;
this.videoQuality = data.video_quality_mode;
this.nsfw = !!data.nsfw;
if (data.rtc_region) {
this.rtcRegion = data.rtc_region;
}
}
bitRate?: number;
userLimit: number;
rtcRegion?: Snowflake;
videoQuality?: VideoQualityModes;
nsfw: boolean;
/**
* This function was gathered from Discordeno it may not work
*/
async connect(options?: UpdateVoiceState) {
const shardId = calculateShardId(this.session.gateway, BigInt(super.guildId));
const shard = this.session.gateway.manager.shards.get(shardId);
if (!shard) {
throw new Error(`Shard (id: ${shardId} not found`);
}
await shard.send({
op: GatewayOpcodes.VoiceStateUpdate,
d: {
guild_id: super.guildId,
channel_id: super.id,
self_mute: Boolean(options?.selfMute),
self_deaf: options?.selfDeaf ?? true,
},
});
}
}
export default VoiceChannel;