royal-selfbot
Version:
A Discord self-bot library. USE WITH EXTREME CAUTION - AGAINST DISCORD TOS.
191 lines (173 loc) • 8.51 kB
JavaScript
// src/managers/ChannelManager.js
const BaseManager = require('./BaseManager');
const Channel = require('../structures/Channel'); // Assumes a base Channel structure exists
// You might need to import specific channel types later if you create them:
// const TextChannel = require('../structures/TextChannel');
// const VoiceChannel = require('../structures/VoiceChannel');
// const CategoryChannel = require('../structures/CategoryChannel');
// const DMChannel = require('../structures/DMChannel');
/**
* Manages API methods for Channels and stores their cache.
* Can be instantiated globally for DM channels or per-guild for guild channels.
* @extends {BaseManager}
*/
class ChannelManager extends BaseManager {
/**
* @param {Client} client The instantiating client
* @param {Guild} [guild] The guild this manager belongs to (null for global/DM channels)
*/
constructor(client, guild = null) {
// Pass the client and the base Channel structure constructor
super(client, Channel);
/**
* The guild this manager belongs to, if any.
* If null, this manager is handling global channels (like DMs).
* @type {?Guild}
*/
this.guild = guild;
}
/**
* Adds or updates a channel in the cache.
* This creates the appropriate channel structure based on the channel type.
* @param {object} data The raw channel data from the API or WebSocket.
* @param {boolean} [cache=true] Whether to cache the channel.
* @returns {Channel} The created or updated Channel instance (or a specific subtype).
* @override
* @protected
*/
_add(data, cache = true) {
const existing = this.cache.get(data.id);
if (existing && existing._patch) {
existing._patch(data);
return existing;
}
// --- Channel Type Handling ---
// Determine the correct channel class based on 'data.type'
// This is a simplified example; a real implementation would use constants for types.
let channel;
switch (data.type) {
// TODO: Replace numbers with constants from util/Constants.js
case 0: // GUILD_TEXT
case 5: // GUILD_NEWS
case 10: // GUILD_NEWS_THREAD
case 11: // GUILD_PUBLIC_THREAD
case 12: // GUILD_PRIVATE_THREAD
// channel = new TextChannel(this.client, data, this.guild); // Example if TextChannel exists
channel = new Channel(this.client, data, this.guild); // Fallback to base Channel
break;
case 1: // DM
// channel = new DMChannel(this.client, data); // Example if DMChannel exists
channel = new Channel(this.client, data); // Fallback to base Channel
break;
case 2: // GUILD_VOICE
case 13: // GUILD_STAGE_VOICE
// channel = new VoiceChannel(this.client, data, this.guild); // Example if VoiceChannel exists
channel = new Channel(this.client, data, this.guild); // Fallback to base Channel
break;
case 3: // GROUP_DM
// channel = new GroupDMChannel(this.client, data); // Example if GroupDMChannel exists
channel = new Channel(this.client, data); // Fallback to base Channel
break;
case 4: // GUILD_CATEGORY
// channel = new CategoryChannel(this.client, data, this.guild); // Example if CategoryChannel exists
channel = new Channel(this.client, data, this.guild); // Fallback to base Channel
break;
// Add cases for other channel types as needed (store, forum, etc.)
default:
// Unknown channel type, use the base Channel class
console.warn(`[ChannelManager] Unknown channel type ${data.type} for channel ${data.id}. Using base Channel structure.`);
channel = new Channel(this.client, data, this.guild); // Pass guild if available, might be null
break;
}
if (cache) this.cache.set(channel.id, channel);
return channel;
}
/**
* Resolves a ChannelResolvable to a Channel object.
* @param {Channel|Snowflake} channelResolvable The channel resolvable to resolve.
* @returns {?Channel} The resolved Channel or null if not found.
* @override
*/
resolve(channelResolvable) {
const channel = super.resolve(channelResolvable); // Use BaseManager's resolve
if (channel) return channel;
if (typeof channelResolvable === 'string' && this.cache.has(channelResolvable)) {
return this.cache.get(channelResolvable);
}
return null;
}
/**
* Resolves a ChannelResolvable to a Channel ID string.
* @param {Channel|Snowflake} channelResolvable The channel resolvable to resolve.
* @returns {?Snowflake} The resolved channel ID or null if not found.
* @override
*/
resolveId(channelResolvable) {
const id = super._resolveId(channelResolvable); // Use BaseManager's resolveId
if (id) return id;
if (typeof channelResolvable === 'string') {
return channelResolvable;
}
return null;
}
/**
* Fetches a channel from Discord, even if it's not cached.
* @param {Snowflake} id The ID of the channel to fetch.
* @param {object} [options={}] Options for fetching.
* @param {boolean} [options.cache=true] Whether to cache the fetched channel.
* @param {boolean} [options.force=false] Whether to skip checking the cache and fetch directly.
* @returns {Promise<Channel>}
*/
async fetch(id, { cache = true, force = false } = {}) {
if (!force) {
// Check this manager's cache first (could be guild-specific or global)
const existing = this.cache.get(id);
// If this is a guild manager, also check the main client channel cache (for potential DMs accessed via guild context?) - might be overly complex.
// const globalExisting = !this.guild && this.client.channels.cache.get(id);
// if (existing || globalExisting) return existing || globalExisting;
if (existing) return existing;
}
try {
const data = await this.client.rest.request('GET', `/channels/${id}`);
// The _add method handles creating the correct channel type and caching
return this._add(data, cache);
} catch (error) {
console.error(`[ChannelManager Fetch Error] Failed to fetch channel ${id}:`, error.response?.data || error.message);
// Handle specific errors, e.g., 404 Not Found, 403 Forbidden
if (error.response?.status === 404) {
// Optionally remove from cache if it was a fetch for an existing but now deleted channel
if (cache) this.cache.delete(id);
return null; // Return null for not found
}
throw error; // Rethrow other errors
}
}
/**
* Fetches all channels for the guild this manager belongs to.
* Only applicable if `this.guild` is set.
* @param {boolean} [cache=true] Whether to cache the fetched channels.
* @returns {Promise<Map<Snowflake, GuildChannel>>} A map of channel IDs to GuildChannel objects.
*/
async fetchGuildChannels(cache = true) {
if (!this.guild) {
throw new Error('Cannot fetch guild channels from a non-guild channel manager.');
}
try {
const channelsData = await this.client.rest.request('GET', `/guilds/${this.guild.id}/channels`);
const fetchedChannels = new Map();
for (const channelData of channelsData) {
const channel = this._add(channelData, cache);
fetchedChannels.set(channel.id, channel);
}
return fetchedChannels;
} catch (error) {
console.error(`[ChannelManager Fetch Error] Failed to fetch channels for guild ${this.guild.id}:`, error.response?.data || error.message);
throw error;
}
}
// You could add methods for creating/deleting channels here, e.g.:
// async create(options) { ... }
// async delete(channelResolvable, reason) { ... }
// Note: Permissions for self-bots are limited.
}
module.exports = ChannelManager;