discord.js-selfbot-v13/src/structures/GuildChannel.js

457 lines
15 KiB
JavaScript
Raw Normal View History

2022-03-19 10:37:45 +00:00
'use strict';
const { PermissionFlagsBits } = require('discord-api-types/v9');
const { Channel } = require('./Channel');
const { Error } = require('../errors');
const PermissionOverwriteManager = require('../managers/PermissionOverwriteManager');
const { VoiceBasedChannelTypes } = require('../util/Constants');
const PermissionsBitField = require('../util/PermissionsBitField');
/**
* Represents a guild channel from any of the following:
* - {@link TextChannel}
* - {@link VoiceChannel}
* - {@link CategoryChannel}
* - {@link NewsChannel}
* - {@link StoreChannel}
* - {@link StageChannel}
* @extends {Channel}
* @abstract
*/
class GuildChannel extends Channel {
constructor(guild, data, client, immediatePatch = true) {
super(guild?.client ?? client, data, false);
/**
* The guild the channel is in
* @type {Guild}
*/
this.guild = guild;
/**
* The id of the guild the channel is in
* @type {Snowflake}
*/
this.guildId = guild?.id ?? data.guild_id;
this.parentId = this.parentId ?? null;
/**
* A manager of permission overwrites that belong to this channel
* @type {PermissionOverwriteManager}
*/
this.permissionOverwrites = new PermissionOverwriteManager(this);
if (data && immediatePatch) this._patch(data);
}
_patch(data) {
super._patch(data);
if ('name' in data) {
/**
* The name of the guild channel
* @type {string}
*/
this.name = data.name;
}
if ('position' in data) {
/**
* The raw position of the channel from Discord
* @type {number}
*/
this.rawPosition = data.position;
}
if ('guild_id' in data) {
this.guildId = data.guild_id;
}
if ('parent_id' in data) {
/**
* The id of the category parent of this channel
* @type {?Snowflake}
*/
this.parentId = data.parent_id;
}
if ('permission_overwrites' in data) {
this.permissionOverwrites.cache.clear();
for (const overwrite of data.permission_overwrites) {
this.permissionOverwrites._add(overwrite);
}
}
}
_clone() {
const clone = super._clone();
clone.permissionOverwrites = new PermissionOverwriteManager(clone, this.permissionOverwrites.cache.values());
return clone;
}
/**
* The category parent of this channel
* @type {?CategoryChannel}
* @readonly
*/
get parent() {
return this.guild.channels.resolve(this.parentId);
}
/**
* If the permissionOverwrites match the parent channel, null if no parent
* @type {?boolean}
* @readonly
*/
get permissionsLocked() {
if (!this.parent) return null;
// Get all overwrites
const overwriteIds = new Set([
...this.permissionOverwrites.cache.keys(),
...this.parent.permissionOverwrites.cache.keys(),
]);
// Compare all overwrites
return [...overwriteIds].every(key => {
const channelVal = this.permissionOverwrites.cache.get(key);
const parentVal = this.parent.permissionOverwrites.cache.get(key);
// Handle empty overwrite
if (
(!channelVal &&
parentVal.deny.bitfield === PermissionsBitField.defaultBit &&
parentVal.allow.bitfield === PermissionsBitField.defaultBit) ||
(!parentVal &&
channelVal.deny.bitfield === PermissionsBitField.defaultBit &&
channelVal.allow.bitfield === PermissionsBitField.defaultBit)
) {
return true;
}
// Compare overwrites
return (
typeof channelVal !== 'undefined' &&
typeof parentVal !== 'undefined' &&
channelVal.deny.bitfield === parentVal.deny.bitfield &&
channelVal.allow.bitfield === parentVal.allow.bitfield
);
});
}
/**
* The position of the channel
* @type {number}
* @readonly
*/
get position() {
const sorted = this.guild._sortedChannels(this);
return [...sorted.values()].indexOf(sorted.get(this.id));
}
/**
* Gets the overall set of permissions for a member or role in this channel, taking into account channel overwrites.
* @param {GuildMemberResolvable|RoleResolvable} memberOrRole The member or role to obtain the overall permissions for
* @param {boolean} [checkAdmin=true] Whether having `ADMINISTRATOR` will return all permissions
* @returns {?Readonly<PermissionsBitField>}
*/
permissionsFor(memberOrRole, checkAdmin = true) {
const member = this.guild.members.resolve(memberOrRole);
if (member) return this.memberPermissions(member, checkAdmin);
const role = this.guild.roles.resolve(memberOrRole);
return role && this.rolePermissions(role, checkAdmin);
}
overwritesFor(member, verified = false, roles = null) {
if (!verified) member = this.guild.members.resolve(member);
if (!member) return [];
roles ??= member.roles.cache;
const roleOverwrites = [];
let memberOverwrites;
let everyoneOverwrites;
for (const overwrite of this.permissionOverwrites.cache.values()) {
if (overwrite.id === this.guild.id) {
everyoneOverwrites = overwrite;
} else if (roles.has(overwrite.id)) {
roleOverwrites.push(overwrite);
} else if (overwrite.id === member.id) {
memberOverwrites = overwrite;
}
}
return {
everyone: everyoneOverwrites,
roles: roleOverwrites,
member: memberOverwrites,
};
}
/**
* Gets the overall set of permissions for a member in this channel, taking into account channel overwrites.
* @param {GuildMember} member The member to obtain the overall permissions for
* @param {boolean} checkAdmin=true Whether having `ADMINISTRATOR` will return all permissions
* @returns {Readonly<PermissionsBitField>}
* @private
*/
memberPermissions(member, checkAdmin) {
if (checkAdmin && member.id === this.guild.ownerId) {
return new PermissionsBitField(PermissionsBitField.All).freeze();
}
const roles = member.roles.cache;
const permissions = new PermissionsBitField(roles.map(role => role.permissions));
if (checkAdmin && permissions.has(PermissionFlagsBits.Administrator)) {
return new PermissionsBitField(PermissionsBitField.All).freeze();
}
const overwrites = this.overwritesFor(member, true, roles);
return permissions
.remove(overwrites.everyone?.deny ?? PermissionsBitField.defaultBit)
.add(overwrites.everyone?.allow ?? PermissionsBitField.defaultBit)
.remove(overwrites.roles.length > 0 ? overwrites.roles.map(role => role.deny) : PermissionsBitField.defaultBit)
.add(overwrites.roles.length > 0 ? overwrites.roles.map(role => role.allow) : PermissionsBitField.defaultBit)
.remove(overwrites.member?.deny ?? PermissionsBitField.defaultBit)
.add(overwrites.member?.allow ?? PermissionsBitField.defaultBit)
.freeze();
}
/**
* Gets the overall set of permissions for a role in this channel, taking into account channel overwrites.
* @param {Role} role The role to obtain the overall permissions for
* @param {boolean} checkAdmin Whether having `ADMINISTRATOR` will return all permissions
* @returns {Readonly<PermissionsBitField>}
* @private
*/
rolePermissions(role, checkAdmin) {
if (checkAdmin && role.permissions.has(PermissionFlagsBits.Administrator)) {
return new PermissionsBitField(PermissionsBitField.All).freeze();
}
const everyoneOverwrites = this.permissionOverwrites.cache.get(this.guild.id);
const roleOverwrites = this.permissionOverwrites.cache.get(role.id);
return role.permissions
.remove(everyoneOverwrites?.deny ?? PermissionsBitField.defaultBit)
.add(everyoneOverwrites?.allow ?? PermissionsBitField.defaultBit)
.remove(roleOverwrites?.deny ?? PermissionsBitField.defaultBit)
.add(roleOverwrites?.allow ?? PermissionsBitField.defaultBit)
.freeze();
}
/**
* Locks in the permission overwrites from the parent channel.
* @returns {Promise<GuildChannel>}
*/
lockPermissions() {
if (!this.parent) return Promise.reject(new Error('GUILD_CHANNEL_ORPHAN'));
const permissionOverwrites = this.parent.permissionOverwrites.cache.map(overwrite => overwrite.toJSON());
return this.edit({ permissionOverwrites });
}
/**
* A collection of cached members of this channel, mapped by their ids.
* Members that can view this channel, if the channel is text-based.
* Members in the channel, if the channel is voice-based.
* @type {Collection<Snowflake, GuildMember>}
* @readonly
*/
get members() {
return this.guild.members.cache.filter(m => this.permissionsFor(m).has(PermissionFlagsBits.ViewChannel, false));
}
/**
* Edits the channel.
* @param {ChannelData} data The new data for the channel
* @param {string} [reason] Reason for editing this channel
* @returns {Promise<GuildChannel>}
* @example
* // Edit a channel
* channel.edit({ name: 'new-channel' })
* .then(console.log)
* .catch(console.error);
*/
edit(data, reason) {
return this.guild.channels.edit(this, data, reason);
}
/**
* Sets a new name for the guild channel.
* @param {string} name The new name for the guild channel
* @param {string} [reason] Reason for changing the guild channel's name
* @returns {Promise<GuildChannel>}
* @example
* // Set a new channel name
* channel.setName('not_general')
* .then(newChannel => console.log(`Channel's new name is ${newChannel.name}`))
* .catch(console.error);
*/
setName(name, reason) {
return this.edit({ name }, reason);
}
/**
* Options used to set the parent of a channel.
* @typedef {Object} SetParentOptions
* @property {boolean} [lockPermissions=true] Whether to lock the permissions to what the parent's permissions are
* @property {string} [reason] The reason for modifying the parent of the channel
*/
/**
* Sets the parent of this channel.
* @param {?CategoryChannelResolvable} channel The category channel to set as the parent
* @param {SetParentOptions} [options={}] The options for setting the parent
* @returns {Promise<GuildChannel>}
* @example
* // Add a parent to a channel
* message.channel.setParent('355908108431917066', { lockPermissions: false })
* .then(channel => console.log(`New parent of ${message.channel.name}: ${channel.name}`))
* .catch(console.error);
*/
setParent(channel, { lockPermissions = true, reason } = {}) {
return this.edit(
{
parent: channel ?? null,
lockPermissions,
},
reason,
);
}
/**
* Options used to set the position of a channel.
* @typedef {Object} SetChannelPositionOptions
* @property {boolean} [relative=false] Whether or not to change the position relative to its current value
* @property {string} [reason] The reason for changing the position
*/
/**
* Sets a new position for the guild channel.
* @param {number} position The new position for the guild channel
* @param {SetChannelPositionOptions} [options] Options for setting position
* @returns {Promise<GuildChannel>}
* @example
* // Set a new channel position
* channel.setPosition(2)
* .then(newChannel => console.log(`Channel's new position is ${newChannel.position}`))
* .catch(console.error);
*/
setPosition(position, options = {}) {
return this.guild.channels.setPosition(this, position, options);
}
/**
* Options used to clone a guild channel.
* @typedef {GuildChannelCreateOptions} GuildChannelCloneOptions
* @property {string} [name=this.name] Name of the new channel
*/
/**
* Clones this channel.
* @param {GuildChannelCloneOptions} [options] The options for cloning this channel
* @returns {Promise<GuildChannel>}
*/
clone(options = {}) {
return this.guild.channels.create(options.name ?? this.name, {
permissionOverwrites: this.permissionOverwrites.cache,
topic: this.topic,
type: this.type,
nsfw: this.nsfw,
parent: this.parent,
bitrate: this.bitrate,
userLimit: this.userLimit,
rateLimitPerUser: this.rateLimitPerUser,
position: this.rawPosition,
reason: null,
...options,
});
}
/**
* Checks if this channel has the same type, topic, position, name, overwrites, and id as another channel.
* In most cases, a simple `channel.id === channel2.id` will do, and is much faster too.
* @param {GuildChannel} channel Channel to compare with
* @returns {boolean}
*/
equals(channel) {
let equal =
channel &&
this.id === channel.id &&
this.type === channel.type &&
this.topic === channel.topic &&
this.position === channel.position &&
this.name === channel.name;
if (equal) {
if (this.permissionOverwrites && channel.permissionOverwrites) {
equal = this.permissionOverwrites.cache.equals(channel.permissionOverwrites.cache);
} else {
equal = !this.permissionOverwrites && !channel.permissionOverwrites;
}
}
return equal;
}
/**
* Whether the channel is deletable by the client user
* @type {boolean}
* @readonly
*/
get deletable() {
return this.manageable && this.guild.rulesChannelId !== this.id && this.guild.publicUpdatesChannelId !== this.id;
}
/**
* Whether the channel is manageable by the client user
* @type {boolean}
* @readonly
*/
get manageable() {
if (this.client.user.id === this.guild.ownerId) return true;
const permissions = this.permissionsFor(this.client.user);
if (!permissions) return false;
// This flag allows managing even if timed out
if (permissions.has(PermissionFlagsBits.Administrator, false)) return true;
if (this.guild.me.communicationDisabledUntilTimestamp > Date.now()) return false;
const bitfield = VoiceBasedChannelTypes.includes(this.type)
? PermissionFlagsBits.ManageChannels | PermissionFlagsBits.Connect
: PermissionFlagsBits.ViewChannel | PermissionFlagsBits.ManageChannels;
return permissions.has(bitfield, false);
}
/**
* Whether the channel is viewable by the client user
* @type {boolean}
* @readonly
*/
get viewable() {
if (this.client.user.id === this.guild.ownerId) return true;
const permissions = this.permissionsFor(this.client.user);
if (!permissions) return false;
return permissions.has(PermissionFlagsBits.ViewChannel, false);
}
/**
* Deletes this channel.
* @param {string} [reason] Reason for deleting this channel
* @returns {Promise<GuildChannel>}
* @example
* // Delete the channel
* channel.delete('making room for new channels')
* .then(console.log)
* .catch(console.error);
*/
async delete(reason) {
await this.guild.channels.delete(this.id, reason);
return this;
}
}
module.exports = GuildChannel;