import {
PERMISSION_OVERWRITE_TYPES,
TO_JSON_TYPES_ENUM,
} from "../constants.js";
import ChannelCacheOptions from "../managers/ChannelCacheOptions.js";
import ChannelMessageManager from "../managers/ChannelMessageManager.js";
import Message from "./Message.js";
import PermissionOverwrite from "./PermissionOverwrite.js";
import GluonCacheOptions from "../managers/GluonCacheOptions.js";
import GuildCacheOptions from "../managers/GuildCacheOptions.js";
import util from "util";
import Member from "./Member.js";
import Client from "../Client.js";
/**
* Represents a channel within Discord.
* @see {@link https://discord.com/developers/docs/resources/channel}
*/
class Channel {
#_client;
#_id;
#_guild_id;
#type;
#name;
#topic;
#permission_overwrites;
#rate_limit_per_user;
#_parent_id;
#_attributes;
#_cacheOptions;
#messages;
/**
* Creates the base structure for a channel.
* @param {Client} client The client instance.
* @param {Object} data Raw channel data.
* @param {Object} options Additional options for this structure.
* @param {String} options.guildId The ID of the guild that this channel belongs to.
* @see {@link https://discord.com/developers/docs/resources/channel#channel-object-channel-structure}
*/
constructor(client, data, { guildId } = {}) {
if (!(client instanceof Client))
throw new TypeError("GLUON: Client must be an instance of Client");
if (typeof data !== "object")
throw new TypeError("GLUON: Data must be an object");
if (typeof guildId !== "string")
throw new TypeError("GLUON: Guild ID must be a string");
/**
* The client instance.
* @type {Client}
* @private
*/
this.#_client = client;
/**
* The id of the channel.
* @type {BigInt}
* @private
*/
this.#_id = BigInt(data.id);
/**
* The ID of the guild that this channel belongs to.
* @type {BigInt}
* @private
*/
this.#_guild_id = BigInt(guildId);
/**
* The type of channel.
* @type {Number}
* @private
*/
this.#type = data.type;
const existing = this.guild?.channels.get(data.id) || null;
/**
* The name of the channel.
* @type {String}
* @private
*/
if (typeof data.name == "string") this.#name = data.name;
else if (
typeof data.name != "string" &&
existing &&
typeof existing.name == "string"
)
this.#name = existing.name;
/**
* The topic of the channel.
* @type {String?}
* @private
*/
if (typeof data.topic == "string") this.#topic = data.topic;
else if (
typeof data.topic != "string" &&
existing &&
typeof existing.topic == "string"
)
this.#topic = existing.topic;
/**
* The permission overwrites for this channel.
* @type {Array<Object>}
* @private
* @see {@link https://discord.com/developers/docs/resources/channel#overwrite-object}
*/
if (data.permission_overwrites && Array.isArray(data.permission_overwrites))
this.#permission_overwrites = data.permission_overwrites.map(
(p) => new PermissionOverwrite(this.#_client, p),
);
else if (
!data.permission_overwrites &&
existing &&
Array.isArray(existing.permissionOverwrites)
)
this.#permission_overwrites = existing.permissionOverwrites;
/**
* The message send cooldown for the channel.
* @type {Number?}
* @private
*/
if (typeof data.rate_limit_per_user == "number")
this.#rate_limit_per_user = data.rate_limit_per_user;
else if (
typeof data.rate_limit_per_user != "number" &&
existing &&
typeof existing.rateLimitPerUser == "number"
)
this.#rate_limit_per_user = existing.rateLimitPerUser;
if (typeof data.parent_id == "string") {
/**
* The id of the parent channel.
* @type {BigInt?}
* @private
*/
this.#_parent_id = BigInt(data.parent_id);
} else if (
typeof data.parent_id != "string" &&
data.parent_id === undefined &&
existing &&
typeof existing.parentId == "string"
)
this.#_parent_id = existing.parentId;
/**
* The attributes of the channel.
* @type {Number}
* @private
*/
this.#_attributes = data._attributes ?? 0;
if (data.nsfw !== undefined && data.nsfw == true)
this.#_attributes |= 0b1 << 0;
else if (data.nsfw === undefined && existing && existing.nsfw == true)
this.#_attributes |= 0b1 << 0;
/**
* The cache options for this channel.
* @type {ChannelCacheOptions}
* @private
*/
this.#_cacheOptions = existing?._cacheOptions
? existing._cacheOptions
: new ChannelCacheOptions(data._cacheOptions);
/**
* The message manager for this channel.
* @type {ChannelMessageManager}
* @private
*/
this.#messages = existing?.messages
? existing.messages
: new ChannelMessageManager(client, this.guild, this);
}
/**
* Sends a message to this channel.
* @param {Object} data Embeds, components and files to include with the message.
* @param {String?} data.content The content of the message.
* @param {Array<Embed>?} data.embeds The embeds to include with the message.
* @param {Array<MessageComponents>?} data.components The components to include with the message.
* @param {Array<FileUpload>?} data.files The files to include with the message.
* @param {Boolean} data.suppressMentions Whether to suppress mentions in the message.
* @returns {Promise<Message>}
* @see {@link https://discord.com/developers/docs/resources/channel#create-message}
* @method
* @public
* @async
* @throws {Error | TypeError}
*/
send(
{ content, components, files, embeds, suppressMentions = false } = {
suppressMentions: false,
},
) {
return Message.send(this.#_client, this.id, this.guildId, {
content,
components,
files,
embeds,
suppressMentions,
});
}
/**
* Returns the mention for this channel.
* @type {String}
* @readonly
* @public
*/
get mention() {
return Channel.getMention(this.id);
}
/**
* Whether this channel is marked as NSFW or not.
* @readonly
* @returns {Boolean}
* @public
*/
get nsfw() {
return (this.#_attributes & (0b1 << 0)) == 0b1 << 0;
}
/**
* The guild that this channel belongs to.
* @type {Guild?}
* @readonly
* @public
*/
get guild() {
return this.#_client.guilds.get(this.guildId) || null;
}
/**
* The parent channel.
* @type {Channel?}
* @readonly
* @public
*/
get parent() {
return this.parentId
? this.guild?.channels.get(this.parentId) || null
: null;
}
/**
* The ID of the channel.
* @type {String}
* @readonly
* @public
*/
get id() {
return String(this.#_id);
}
/**
* The ID of the guild that this channel belongs to.
* @type {String}
* @readonly
* @public
*/
get guildId() {
return String(this.#_guild_id);
}
/**
* The ID of the parent channel.
* @type {String?}
* @readonly
* @public
*/
get parentId() {
return this.#_parent_id ? String(this.#_parent_id) : null;
}
/**
* The type of channel.
* @type {Number}
* @readonly
* @public
*/
get type() {
return this.#type;
}
/**
* The name of the channel.
* @type {String?}
* @readonly
* @public
*/
get name() {
return this.#name;
}
/**
* The topic of the channel.
* @type {String?}
* @readonly
* @public
*/
get topic() {
return this.#topic;
}
/**
* The permission overwrites for this channel.
* @type {Array<Object>}
* @readonly
* @public
*/
get permissionOverwrites() {
return this.#permission_overwrites;
}
/**
* The message send cooldown for the channel.
* @type {Number?}
* @readonly
* @public
*/
get rateLimitPerUser() {
return this.#rate_limit_per_user;
}
/**
* The cache options for this channel.
* @type {ChannelCacheOptions}
* @readonly
* @public
*/
get _cacheOptions() {
return this.#_cacheOptions;
}
/**
* The messages in this channel.
* @type {ChannelMessageManager}
* @readonly
* @public
*/
get messages() {
return this.#messages;
}
/**
* Returns the mention string for a channel.
* @param {String} channelId The ID of the channel to mention.
* @returns {String}
* @public
* @static
* @method
*/
static getMention(channelId) {
if (!channelId) throw new TypeError("GLUON: No channel ID provided.");
return `<#${channelId}>`;
}
/**
* Determines whether the channel should be cached.
* @param {GluonCacheOptions} gluonCacheOptions The cache options for the client.
* @param {GuildCacheOptions} guildCacheOptions The cache options for the guild.
* @returns {Boolean}
* @public
* @static
* @method
*/
static shouldCache(gluonCacheOptions, guildCacheOptions) {
if (!(gluonCacheOptions instanceof GluonCacheOptions))
throw new TypeError(
"GLUON: Gluon cache options must be a GluonCacheOptions.",
);
if (!(guildCacheOptions instanceof GuildCacheOptions))
throw new TypeError(
"GLUON: Guild cache options must be a GuildCacheOptions.",
);
if (gluonCacheOptions.cacheChannels === false) return false;
if (guildCacheOptions.channelCaching === false) return false;
return true;
}
/**
* Follows a news channel.
* @param {Client} client The client instance.
* @param {String} channelId The ID of the channel.
* @param {String} followChannelId THe ID of the channel to follow.
* @returns {Promise<void>}
* @public
* @static
* @async
* @method
* @throws {TypeError}
*/
static async follow(client, channelId, followChannelId) {
if (!(client instanceof Client))
throw new TypeError("GLUON: Client must be a Client instance.");
if (typeof channelId !== "string")
throw new TypeError("GLUON: Channel ID is not a string.");
if (typeof followChannelId !== "string")
throw new TypeError("GLUON: Follow channel ID is not a string.");
const body = {};
body.webhook_channel_id = channelId;
await client.request.makeRequest(
"postFollowNewsChannel",
[followChannelId],
body,
);
}
/**
* Returns an array of webhooks for the specified channel.
* @param {Client} client The client instance.
* @param {String} channelId The ID of the channel.
* @returns {Promise<Array<Object>>}
* @public
* @static
* @async
* @method
*/
static fetchWebhooks(client, channelId) {
if (!(client instanceof Client))
throw new TypeError("GLUON: Client must be a Client instance.");
if (typeof channelId !== "string")
throw new TypeError("GLUON: Channel ID is not a string.");
return client.request.makeRequest("getChannelWebhooks", [channelId]);
}
/**
* Returns the permissions for a member in this channel.
* @param {Member} member The member to check the permissions for.
* @returns {String}
*/
checkPermission(member) {
if (!member) throw new TypeError("GLUON: No member provided.");
if (!(member instanceof Member))
throw new TypeError("GLUON: Member must be a Member.");
let overallPermissions = BigInt(member.permissions);
const everyoneRole = this.permissionOverwrites.find(
(p) =>
p.id === this.guildId && p.type === PERMISSION_OVERWRITE_TYPES.ROLE,
);
if (everyoneRole) {
overallPermissions &= ~BigInt(everyoneRole.deny);
overallPermissions |= BigInt(everyoneRole.allow);
}
let overallRoleDenyPermissions = BigInt(0);
let overallRoleAllowPermissions = BigInt(0);
for (let i = 0; i < member.roles.length; i++) {
const role = this.permissionOverwrites.find(
(p) =>
p.id === member.roles[i].id &&
p.type === PERMISSION_OVERWRITE_TYPES.ROLE,
);
if (role) {
overallRoleDenyPermissions |= BigInt(role.deny);
overallRoleAllowPermissions |= BigInt(role.allow);
}
}
overallPermissions &= ~overallRoleDenyPermissions;
overallPermissions |= overallRoleAllowPermissions;
const memberOverwritePermissions = this.permissionOverwrites.find(
(p) => p.id === member.id && p.type === PERMISSION_OVERWRITE_TYPES.MEMBER,
);
if (memberOverwritePermissions) {
overallPermissions &= ~BigInt(memberOverwritePermissions.deny);
overallPermissions |= BigInt(memberOverwritePermissions.allow);
}
return String(overallPermissions);
}
/**
* @method
* @public
*/
toString() {
return `<Channel: ${this.id}>`;
}
/**
* @method
* @public
*/
[util.inspect.custom]() {
return this.toString();
}
/**
* Returns the JSON representation of this structure.
* @param {Number} format The format to return the data in.
* @returns {Object}
* @public
* @method
*/
toJSON(format) {
switch (format) {
case TO_JSON_TYPES_ENUM.STORAGE_FORMAT:
case TO_JSON_TYPES_ENUM.CACHE_FORMAT: {
return {
id: this.id,
type: this.type,
name: this.name,
topic: this.topic,
rate_limit_per_user: this.rateLimitPerUser,
parent_id: this.parentId ?? undefined,
_attributes: this.#_attributes,
_cacheOptions: this._cacheOptions.toJSON(format),
messages: this.messages.toJSON(format),
permission_overwrites: this.permissionOverwrites.map((p) =>
p.toJSON(format),
),
};
}
case TO_JSON_TYPES_ENUM.DISCORD_FORMAT:
default: {
return {
id: this.id,
type: this.type,
name: this.name,
topic: this.topic,
rate_limit_per_user: this.rateLimitPerUser,
parent_id: this.parentId ?? undefined,
nsfw: this.nsfw,
messages: this.messages.toJSON(format),
permission_overwrites: this.permissionOverwrites.map((p) =>
p.toJSON(format),
),
};
}
}
}
}
export default Channel;