mirror of
https://github.com/tiramisulabs/seyfert.git
synced 2025-07-03 05:26:07 +00:00
553 lines
18 KiB
TypeScript
553 lines
18 KiB
TypeScript
import {
|
|
ChannelFlags,
|
|
ChannelType,
|
|
VideoQualityMode,
|
|
type APIDMChannel,
|
|
type APIGuildCategoryChannel,
|
|
type APIGuildForumChannel,
|
|
type APIGuildMediaChannel,
|
|
type APIGuildStageVoiceChannel,
|
|
type APIGuildVoiceChannel,
|
|
type APINewsChannel,
|
|
type APITextChannel,
|
|
type APIThreadChannel,
|
|
type ThreadAutoArchiveDuration,
|
|
} from 'discord-api-types/v10';
|
|
import { mix } from 'ts-mixer';
|
|
import { Embed, resolveAttachment } from '../builders';
|
|
import type { BaseClient } from '../client/base';
|
|
import type {
|
|
APIChannelBase,
|
|
APIGuildChannel,
|
|
APIGuildForumDefaultReactionEmoji,
|
|
APIGuildForumTag,
|
|
EmojiResolvable,
|
|
MessageCreateBodyRequest,
|
|
MessageUpdateBodyRequest,
|
|
MethodContext,
|
|
ObjectToLower,
|
|
RESTGetAPIChannelMessageReactionUsersQuery,
|
|
RESTPatchAPIChannelJSONBody,
|
|
RESTPatchAPIGuildChannelPositionsJSONBody,
|
|
RESTPostAPIChannelWebhookJSONBody,
|
|
RESTPostAPIGuildChannelJSONBody,
|
|
SortOrderType,
|
|
StringToNumber,
|
|
ToClass,
|
|
} from '../common';
|
|
import { ComponentsListener } from '../components';
|
|
import type { GuildMember } from './GuildMember';
|
|
import type { GuildRole } from './GuildRole';
|
|
import { Webhook } from './Webhook';
|
|
import { DiscordBase } from './extra/DiscordBase';
|
|
import { channelLink } from './extra/functions';
|
|
|
|
export class BaseChannel<T extends ChannelType> extends DiscordBase<APIChannelBase<ChannelType>> {
|
|
declare type: T;
|
|
|
|
constructor(client: BaseClient, data: APIChannelBase<ChannelType>) {
|
|
super(client, data);
|
|
}
|
|
|
|
static __intent__(id: '@me'): 'DirectMessages';
|
|
static __intent__(id: string): 'DirectMessages' | 'Guilds';
|
|
static __intent__(id: string) {
|
|
return id === '@me' ? 'DirectMessages' : 'Guilds';
|
|
}
|
|
|
|
/** The URL to the channel */
|
|
get url() {
|
|
return channelLink(this.id);
|
|
}
|
|
|
|
fetch(force = false) {
|
|
return this.client.channels.fetch(this.id, force);
|
|
}
|
|
|
|
delete(reason?: string) {
|
|
return this.client.channels.delete(this.id, { reason });
|
|
}
|
|
|
|
edit(body: RESTPatchAPIChannelJSONBody, reason?: string) {
|
|
return this.client.channels.edit(this.id, body, {
|
|
reason,
|
|
guildId: 'guildId' in this ? (this.guildId as string) : '@me',
|
|
});
|
|
}
|
|
|
|
toString() {
|
|
return `<#${this.id}>`;
|
|
}
|
|
|
|
isStage(): this is StageChannel {
|
|
return this.type === ChannelType.GuildStageVoice;
|
|
}
|
|
|
|
isMedia(): this is MediaChannel {
|
|
return this.type === ChannelType.GuildMedia;
|
|
}
|
|
|
|
isDM(): this is DMChannel {
|
|
return [ChannelType.DM, ChannelType.GroupDM].includes(this.type);
|
|
}
|
|
|
|
isForum(): this is ForumChannel {
|
|
return this.type === ChannelType.GuildForum;
|
|
}
|
|
|
|
isThread(): this is ThreadChannel {
|
|
return [ChannelType.PublicThread, ChannelType.PrivateThread, ChannelType.AnnouncementThread].includes(this.type);
|
|
}
|
|
|
|
isDirectory(): this is DirectoryChannel {
|
|
return this.type === ChannelType.GuildDirectory;
|
|
}
|
|
|
|
isVoice(): this is VoiceChannel {
|
|
return this.type === ChannelType.GuildVoice;
|
|
}
|
|
|
|
isTextGuild(): this is TextGuildChannel {
|
|
return this.type === ChannelType.GuildText;
|
|
}
|
|
|
|
isCategory(): this is CategoryChannel {
|
|
return this.type === ChannelType.GuildCategory;
|
|
}
|
|
|
|
isNews(): this is NewsChannel {
|
|
return this.type === ChannelType.GuildAnnouncement;
|
|
}
|
|
|
|
isTextable(): this is AllTextableChannels {
|
|
return 'messages' in this;
|
|
}
|
|
|
|
isGuildTextable(): this is AllGuildTextableChannels {
|
|
return !this.isDM() && this.isTextable();
|
|
}
|
|
|
|
isThreadOnly(): this is ForumChannel | MediaChannel {
|
|
return this.isForum() || this.isMedia();
|
|
}
|
|
|
|
is<T extends (keyof IChannelTypes)[]>(channelTypes: T): this is IChannelTypes[T[number]] {
|
|
return channelTypes.some(x => ChannelType[x] === this.type);
|
|
}
|
|
|
|
static allMethods(ctx: MethodContext<{ guildId: string }>) {
|
|
return {
|
|
list: (force = false) => ctx.client.guilds.channels.list(ctx.guildId, force),
|
|
fetch: (id: string, force = false) => ctx.client.guilds.channels.fetch(ctx.guildId, id, force),
|
|
create: (body: RESTPostAPIGuildChannelJSONBody) => ctx.client.guilds.channels.create(ctx.guildId, body),
|
|
delete: (id: string, reason?: string) => ctx.client.guilds.channels.delete(ctx.guildId, id, reason),
|
|
edit: (id: string, body: RESTPatchAPIChannelJSONBody, reason?: string) =>
|
|
ctx.client.guilds.channels.edit(ctx.guildId, id, body, reason),
|
|
editPositions: (body: RESTPatchAPIGuildChannelPositionsJSONBody) =>
|
|
ctx.client.guilds.channels.editPositions(ctx.guildId, body),
|
|
};
|
|
}
|
|
}
|
|
|
|
interface IChannelTypes {
|
|
GuildStageVoice: StageChannel;
|
|
GuildMedia: MediaChannel;
|
|
DM: DMChannel;
|
|
GuildForum: ForumChannel;
|
|
AnnouncementThread: ThreadChannel;
|
|
PrivateThread: ThreadChannel;
|
|
PublicThread: ThreadChannel;
|
|
GuildDirectory: DirectoryChannel;
|
|
GuildVoice: VoiceChannel;
|
|
GuildText: TextGuildChannel;
|
|
GuildCategory: CategoryChannel;
|
|
GuildAnnouncement: NewsChannel;
|
|
}
|
|
|
|
export interface BaseGuildChannel extends ObjectToLower<Omit<APIGuildChannel<ChannelType>, 'permission_overwrites'>> {}
|
|
export class BaseGuildChannel extends BaseChannel<ChannelType> {
|
|
constructor(client: BaseClient, data: APIGuildChannel<ChannelType>) {
|
|
const { permission_overwrites, ...rest } = data;
|
|
super(client, rest);
|
|
}
|
|
|
|
permissionOverwrites = {
|
|
fetch: () => this.client.cache.overwrites?.get(this.id),
|
|
values: () => (this.guildId ? this.client.cache.overwrites?.values(this.guildId) ?? [] : []),
|
|
};
|
|
|
|
memberPermissions(member: GuildMember, checkAdmin = true) {
|
|
return this.client.channels.overwrites.memberPermissions(this.id, member, checkAdmin);
|
|
}
|
|
|
|
rolePermissions(role: GuildRole, checkAdmin = true) {
|
|
return this.client.channels.overwrites.rolePermissions(this.id, role, checkAdmin);
|
|
}
|
|
|
|
overwritesFor(member: GuildMember) {
|
|
return this.client.channels.overwrites.overwritesFor(this.id, member);
|
|
}
|
|
|
|
guild(force = false) {
|
|
return this.client.guilds.fetch(this.guildId!, force);
|
|
}
|
|
|
|
get url() {
|
|
return channelLink(this.id, this.guildId);
|
|
}
|
|
|
|
setPosition(position: number, reason?: string) {
|
|
return this.edit({ position }, reason);
|
|
}
|
|
|
|
setName(name: string, reason?: string) {
|
|
return this.edit({ name }, reason);
|
|
}
|
|
|
|
setParent(parent_id: string | null, reason?: string) {
|
|
return this.edit({ parent_id }, reason);
|
|
}
|
|
}
|
|
|
|
export interface MessagesMethods extends BaseChannel<ChannelType> {}
|
|
export class MessagesMethods extends DiscordBase {
|
|
typing() {
|
|
return this.client.channels.typing(this.id);
|
|
}
|
|
|
|
messages = MessagesMethods.messages({ client: this.client, channelId: this.id });
|
|
pins = MessagesMethods.pins({ client: this.client, channelId: this.id });
|
|
reactions = MessagesMethods.reactions({ client: this.client, channelId: this.id });
|
|
|
|
static messages(ctx: MethodContext<{ channelId: string }>) {
|
|
return {
|
|
write: (body: MessageCreateBodyRequest) => ctx.client.messages.write(ctx.channelId, body),
|
|
edit: (messageId: string, body: MessageUpdateBodyRequest) =>
|
|
ctx.client.messages.edit(messageId, ctx.channelId, body),
|
|
crosspost: (messageId: string, reason?: string) =>
|
|
ctx.client.messages.crosspost(messageId, ctx.channelId, reason),
|
|
delete: (messageId: string, reason?: string) => ctx.client.messages.delete(messageId, ctx.channelId, reason),
|
|
fetch: (messageId: string) => ctx.client.messages.fetch(messageId, ctx.channelId),
|
|
purge: (messages: string[], reason?: string) => ctx.client.messages.purge(messages, ctx.channelId, reason),
|
|
};
|
|
}
|
|
|
|
static reactions(ctx: MethodContext<{ channelId: string }>) {
|
|
return {
|
|
add: (messageId: string, emoji: EmojiResolvable) =>
|
|
ctx.client.messages.reactions.add(messageId, ctx.channelId, emoji),
|
|
delete: (messageId: string, emoji: EmojiResolvable, userId = '@me') =>
|
|
ctx.client.messages.reactions.delete(messageId, ctx.channelId, emoji, userId),
|
|
fetch: (messageId: string, emoji: EmojiResolvable, query?: RESTGetAPIChannelMessageReactionUsersQuery) =>
|
|
ctx.client.messages.reactions.fetch(messageId, ctx.channelId, emoji, query),
|
|
purge: (messageId: string, emoji?: EmojiResolvable) =>
|
|
ctx.client.messages.reactions.purge(messageId, ctx.channelId, emoji),
|
|
};
|
|
}
|
|
static pins(ctx: MethodContext<{ channelId: string }>) {
|
|
return {
|
|
fetch: () => ctx.client.channels.pins.fetch(ctx.channelId),
|
|
set: (messageId: string, reason?: string) => ctx.client.channels.pins.set(messageId, ctx.channelId, reason),
|
|
delete: (messageId: string, reason?: string) => ctx.client.channels.pins.delete(messageId, ctx.channelId, reason),
|
|
};
|
|
}
|
|
|
|
static transformMessageBody<T>(body: MessageCreateBodyRequest | MessageUpdateBodyRequest) {
|
|
return {
|
|
...body,
|
|
components: body.components
|
|
? (body?.components instanceof ComponentsListener ? body.components.components : body.components).map(x =>
|
|
'toJSON' in x ? x.toJSON() : x,
|
|
)
|
|
: undefined,
|
|
embeds: body.embeds?.map(x => (x instanceof Embed ? x.toJSON() : x)) ?? undefined,
|
|
//?
|
|
attachments: body.attachments?.map((x, i) => ({ id: i, ...resolveAttachment(x) })) ?? undefined,
|
|
} as T;
|
|
}
|
|
}
|
|
|
|
export interface TextBaseGuildChannel
|
|
extends ObjectToLower<Omit<APITextChannel, 'type' | 'permission_overwrites'>>,
|
|
MessagesMethods {}
|
|
@mix(MessagesMethods)
|
|
export class TextBaseGuildChannel extends BaseGuildChannel {}
|
|
|
|
export default function channelFrom(data: APIChannelBase<ChannelType>, client: BaseClient): AllChannels {
|
|
switch (data.type) {
|
|
case ChannelType.GuildStageVoice:
|
|
return new StageChannel(client, data);
|
|
case ChannelType.GuildMedia:
|
|
return new MediaChannel(client, data);
|
|
case ChannelType.DM:
|
|
return new DMChannel(client, data);
|
|
case ChannelType.GuildForum:
|
|
return new ForumChannel(client, data);
|
|
case ChannelType.AnnouncementThread:
|
|
case ChannelType.PrivateThread:
|
|
case ChannelType.PublicThread:
|
|
return new ThreadChannel(client, data);
|
|
case ChannelType.GuildDirectory:
|
|
return new DirectoryChannel(client, data);
|
|
case ChannelType.GuildVoice:
|
|
return new VoiceChannel(client, data);
|
|
case ChannelType.GuildText:
|
|
return new TextGuildChannel(client, data as APIGuildChannel<ChannelType>);
|
|
case ChannelType.GuildCategory:
|
|
return new CategoryChannel(client, data);
|
|
case ChannelType.GuildAnnouncement:
|
|
return new NewsChannel(client, data);
|
|
default:
|
|
if ('guild_id' in data) {
|
|
return new BaseGuildChannel(client, data as APIGuildChannel<ChannelType>);
|
|
}
|
|
return new BaseChannel(client, data);
|
|
}
|
|
}
|
|
|
|
export interface TopicableGuildChannel extends BaseChannel<ChannelType> {}
|
|
export class TopicableGuildChannel extends DiscordBase {
|
|
setTopic(topic: string | null, reason?: string) {
|
|
return this.edit({ topic }, reason);
|
|
}
|
|
}
|
|
|
|
export interface ThreadOnlyMethods extends BaseChannel<ChannelType>, TopicableGuildChannel {}
|
|
@mix(TopicableGuildChannel)
|
|
export class ThreadOnlyMethods extends DiscordBase {
|
|
setTags(tags: APIGuildForumTag[], reason?: string) {
|
|
return this.edit({ available_tags: tags }, reason);
|
|
}
|
|
|
|
setAutoArchiveDuration(duration: ThreadAutoArchiveDuration, reason?: string) {
|
|
return this.edit({ default_auto_archive_duration: duration }, reason);
|
|
}
|
|
|
|
setReactionEmoji(emoji: APIGuildForumDefaultReactionEmoji, reason?: string) {
|
|
return this.edit({ default_reaction_emoji: emoji }, reason);
|
|
}
|
|
|
|
setSortOrder(sort: SortOrderType, reason?: string) {
|
|
return this.edit({ default_sort_order: sort }, reason);
|
|
}
|
|
|
|
setThreadRateLimit(rate: number, reason?: string) {
|
|
return this.edit({ default_thread_rate_limit_per_user: rate }, reason);
|
|
}
|
|
}
|
|
|
|
export interface VoiceChannelMethods extends BaseChannel<ChannelType> {}
|
|
export class VoiceChannelMethods extends DiscordBase {
|
|
setBitrate(bitrate: number | null, reason?: string) {
|
|
return this.edit({ bitrate }, reason);
|
|
}
|
|
|
|
setUserLimit(user_limit: number | null, reason?: string) {
|
|
return this.edit({ user_limit: user_limit ?? 0 }, reason);
|
|
}
|
|
|
|
setRTC(rtc_region: string | null, reason?: string) {
|
|
return this.edit({ rtc_region }, reason);
|
|
}
|
|
|
|
setVideoQuality(quality: keyof typeof VideoQualityMode, reason?: string) {
|
|
return this.edit({ video_quality_mode: VideoQualityMode[quality] }, reason);
|
|
}
|
|
}
|
|
|
|
export class WebhookGuildMethods extends DiscordBase {
|
|
webhooks = WebhookGuildMethods.guild({ client: this.client, guildId: this.id });
|
|
|
|
static guild(ctx: MethodContext<{ guildId: string }>) {
|
|
return {
|
|
list: async () => {
|
|
const webhooks = await ctx.client.proxy.guilds(ctx.guildId).webhooks.get();
|
|
return webhooks.map(webhook => new Webhook(ctx.client, webhook));
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
export class WebhookChannelMethods extends DiscordBase {
|
|
webhooks = WebhookChannelMethods.channel({ client: this.client, channelId: this.id });
|
|
|
|
static channel(ctx: MethodContext<{ channelId: string }>) {
|
|
return {
|
|
list: async () => {
|
|
const webhooks = await ctx.client.proxy.channels(ctx.channelId).webhooks.get();
|
|
return webhooks.map(webhook => new Webhook(ctx.client, webhook));
|
|
},
|
|
create: async (body: RESTPostAPIChannelWebhookJSONBody) => {
|
|
const webhook = await ctx.client.proxy.channels(ctx.channelId).webhooks.post({
|
|
body,
|
|
});
|
|
return new Webhook(ctx.client, webhook);
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
export interface TextGuildChannel
|
|
extends ObjectToLower<Omit<APITextChannel, 'type' | 'permission_overwrites'>>,
|
|
BaseGuildChannel,
|
|
TextBaseGuildChannel,
|
|
WebhookChannelMethods {}
|
|
@mix(TextBaseGuildChannel, WebhookChannelMethods)
|
|
export class TextGuildChannel extends BaseGuildChannel {
|
|
declare type: ChannelType.GuildText;
|
|
|
|
setRatelimitPerUser(rate_limit_per_user: number | null | undefined) {
|
|
return this.edit({ rate_limit_per_user });
|
|
}
|
|
|
|
setNsfw(nsfw = true, reason?: string) {
|
|
return this.edit({ nsfw }, reason);
|
|
}
|
|
}
|
|
|
|
export interface DMChannel extends ObjectToLower<APIDMChannel>, Omit<MessagesMethods, 'edit'> {}
|
|
@mix(MessagesMethods)
|
|
export class DMChannel extends (BaseChannel<ChannelType.DM> as unknown as ToClass<
|
|
Omit<BaseChannel<ChannelType.DM>, 'edit'>,
|
|
DMChannel
|
|
>) {
|
|
declare type: ChannelType.DM;
|
|
}
|
|
export interface VoiceChannel
|
|
extends ObjectToLower<Omit<APIGuildVoiceChannel, 'permission_overwrites'>>,
|
|
Omit<TextGuildChannel, 'type'>,
|
|
VoiceChannelMethods,
|
|
WebhookChannelMethods {}
|
|
@mix(TextGuildChannel, WebhookChannelMethods, VoiceChannelMethods)
|
|
export class VoiceChannel extends BaseChannel<ChannelType.GuildVoice> {
|
|
declare type: ChannelType.GuildVoice;
|
|
}
|
|
|
|
export interface StageChannel
|
|
extends ObjectToLower<Omit<APIGuildStageVoiceChannel, 'type'>>,
|
|
TopicableGuildChannel,
|
|
VoiceChannelMethods {}
|
|
@mix(TopicableGuildChannel, VoiceChannelMethods)
|
|
export class StageChannel extends BaseChannel<ChannelType> {
|
|
declare type: ChannelType.GuildStageVoice;
|
|
}
|
|
|
|
export interface MediaChannel extends ObjectToLower<Omit<APIGuildMediaChannel, 'type'>>, ThreadOnlyMethods {}
|
|
@mix(ThreadOnlyMethods)
|
|
export class MediaChannel extends BaseChannel<ChannelType> {
|
|
declare type: ChannelType.GuildMedia;
|
|
}
|
|
|
|
export interface ForumChannel
|
|
extends ObjectToLower<APIGuildForumChannel>,
|
|
Omit<ThreadOnlyMethods, 'type'>,
|
|
WebhookChannelMethods {}
|
|
@mix(ThreadOnlyMethods, WebhookChannelMethods)
|
|
export class ForumChannel extends BaseChannel<ChannelType.GuildForum> {
|
|
declare type: ChannelType.GuildForum;
|
|
}
|
|
|
|
export interface ThreadChannel
|
|
extends ObjectToLower<Omit<APIThreadChannel, 'permission_overwrites'>>,
|
|
TextBaseGuildChannel {}
|
|
@mix(TextBaseGuildChannel)
|
|
export class ThreadChannel extends BaseChannel<
|
|
ChannelType.PublicThread | ChannelType.AnnouncementThread | ChannelType.PrivateThread
|
|
> {
|
|
declare type: ChannelType.PublicThread | ChannelType.AnnouncementThread | ChannelType.PrivateThread;
|
|
webhooks = WebhookChannelMethods.channel({
|
|
client: this.client,
|
|
channelId: this.parentId!,
|
|
});
|
|
|
|
setRatelimitPerUser(rate_limit_per_user: number | null | undefined) {
|
|
return this.edit({ rate_limit_per_user });
|
|
}
|
|
|
|
pin(reason?: string) {
|
|
return this.edit({ flags: (this.flags ?? 0) | ChannelFlags.Pinned }, reason);
|
|
}
|
|
|
|
unpin(reason?: string) {
|
|
return this.edit({ flags: (this.flags ?? 0) & ~ChannelFlags.Pinned }, reason);
|
|
}
|
|
|
|
setTags(applied_tags: string[], reason?: string) {
|
|
/**
|
|
* The available_tags field can be set when creating or updating a channel.
|
|
* Which determines which tags can be set on individual threads within the thread's applied_tags field.
|
|
*/
|
|
return this.edit({ applied_tags }, reason);
|
|
}
|
|
|
|
setArchived(archived = true, reason?: string) {
|
|
return this.edit({ archived }, reason);
|
|
}
|
|
|
|
setAutoArchiveDuration(auto_archive_duration: StringToNumber<`${ThreadAutoArchiveDuration}`>, reason?: string) {
|
|
return this.edit({ auto_archive_duration }, reason);
|
|
}
|
|
|
|
setInvitable(invitable = true, reason?: string) {
|
|
return this.edit({ invitable }, reason);
|
|
}
|
|
|
|
setLocked(locked = true, reason?: string) {
|
|
return this.edit({ locked }, reason);
|
|
}
|
|
}
|
|
|
|
export interface CategoryChannel extends ObjectToLower<Omit<APIGuildCategoryChannel, 'permission_overwrites'>> {}
|
|
|
|
export class CategoryChannel extends (BaseGuildChannel as unknown as ToClass<
|
|
Omit<BaseGuildChannel, 'setParent' | 'type'>,
|
|
CategoryChannel
|
|
>) {
|
|
declare type: ChannelType.GuildCategory;
|
|
}
|
|
|
|
export interface NewsChannel extends ObjectToLower<APINewsChannel>, WebhookChannelMethods {}
|
|
@mix(WebhookChannelMethods)
|
|
export class NewsChannel extends BaseChannel<ChannelType.GuildAnnouncement> {
|
|
declare type: ChannelType.GuildAnnouncement;
|
|
|
|
addFollower(webhook_channel_id: string, reason?: string) {
|
|
return this.api.channels(this.id).followers.post({
|
|
body: {
|
|
webhook_channel_id,
|
|
},
|
|
reason,
|
|
});
|
|
}
|
|
}
|
|
|
|
export class DirectoryChannel extends BaseChannel<ChannelType.GuildDirectory> {}
|
|
|
|
export type AllGuildChannels =
|
|
| TextGuildChannel
|
|
| VoiceChannel
|
|
| MediaChannel
|
|
| ForumChannel
|
|
| ThreadChannel
|
|
| CategoryChannel
|
|
| NewsChannel
|
|
| DirectoryChannel
|
|
| StageChannel;
|
|
|
|
export type AllTextableChannels = TextGuildChannel | VoiceChannel | DMChannel | NewsChannel | ThreadChannel;
|
|
export type AllGuildTextableChannels = TextGuildChannel | VoiceChannel | NewsChannel | ThreadChannel;
|
|
|
|
export type AllChannels =
|
|
| BaseChannel<ChannelType>
|
|
| BaseGuildChannel
|
|
| TextGuildChannel
|
|
| DMChannel
|
|
| VoiceChannel
|
|
| MediaChannel
|
|
| ForumChannel
|
|
| ThreadChannel
|
|
| CategoryChannel
|
|
| NewsChannel
|
|
| DirectoryChannel
|
|
| StageChannel;
|