Merge branch 'main' of https://github.com/NekoCyan/discord.js-selfbot-v13
This commit is contained in:
commit
5563e23664
@ -13,6 +13,9 @@
|
||||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"**/src/util/Voice.js"
|
||||
],
|
||||
"rules": {
|
||||
"import/order": [
|
||||
"error",
|
||||
@ -283,4 +286,4 @@
|
||||
],
|
||||
"linebreak-style": 0
|
||||
}
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@
|
||||
|
||||
## About
|
||||
|
||||
<strong>Welcome to `discord.js-selfbot-v13@v2.6`, based on `discord.js@13.11.0`</strong>
|
||||
<strong>Welcome to `discord.js-selfbot-v13@v2.8`, based on `discord.js@13.11`</strong>
|
||||
|
||||
- discord.js-selfbot-v13 is a [Node.js](https://nodejs.org) module that allows user accounts to interact with the Discord API v9.
|
||||
|
||||
@ -68,7 +68,7 @@ client.login('token');
|
||||
|
||||
## Get Token ?
|
||||
|
||||
<strong>Copy code to console Discord [Ctrl + Shift + I]</strong>
|
||||
<strong>Run code (Discord Console - [Ctrl + Shift + I])</strong>
|
||||
|
||||
```js
|
||||
window.webpackChunkdiscord_app.push([
|
||||
|
File diff suppressed because one or more lines are too long
11
package.json
11
package.json
@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "discord.js-selfbot-v13",
|
||||
"version": "2.6.15",
|
||||
"version": "2.8.0",
|
||||
"description": "A unofficial discord.js fork for creating selfbots [Based on discord.js v13]",
|
||||
"main": "./src/index.js",
|
||||
"types": "./typings/index.d.ts",
|
||||
"scripts": {
|
||||
"all": "npm run lint:fix && npm run lint:typings:fix && npm run fix:all && npm run build",
|
||||
"all": "npm run lint:fix && npm run lint:typings:fix && npm run fix:all && npm run build && npm publish",
|
||||
"test": "npm run lint:all && npm run docs:test && npm run test:typescript",
|
||||
"fix:all": "npm run lint:fix && npm run lint:typings:fix && npm run format",
|
||||
"test:typescript": "tsc --noEmit && tsd",
|
||||
@ -62,14 +62,15 @@
|
||||
"axios": "^0.27.2",
|
||||
"bignumber.js": "^9.1.0",
|
||||
"chalk": "^4.1.2",
|
||||
"discord-api-types": "^0.37.8",
|
||||
"discord-api-types": "^0.37.11",
|
||||
"form-data": "^4.0.0",
|
||||
"json-bigint": "^1.0.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"proxy-agent": "^5.0.0",
|
||||
"safe-base64": "^2.0.1-0",
|
||||
"string_decoder": "^1.3.0",
|
||||
"ws": "^8.8.1"
|
||||
"string-similarity": "^4.0.4",
|
||||
"ws": "^8.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.6.0",
|
||||
@ -83,7 +84,7 @@
|
||||
"@types/node": "^16.11.12",
|
||||
"conventional-changelog-cli": "^2.2.2",
|
||||
"dtslint": "^4.2.1",
|
||||
"eslint": "^8.23.0",
|
||||
"eslint": "^8.24.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-import": "^2.25.3",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
|
@ -204,6 +204,8 @@ class Client extends BaseClient {
|
||||
this.token = null;
|
||||
}
|
||||
|
||||
this._interactionCache = new Collection();
|
||||
|
||||
/**
|
||||
* User that the client is logged in as
|
||||
* @type {?ClientUser}
|
||||
@ -340,7 +342,7 @@ class Client extends BaseClient {
|
||||
this.emit(Events.DEBUG, `Added Fingerprint: ${res.data.fingerprint}`);
|
||||
})
|
||||
.catch(err => {
|
||||
this.emit(Events.DEBUG, `Finding Cookie and Fingerprint failed: ${err.message}`);
|
||||
this.emit(Events.DEBUG, `Update Cookie and Fingerprint failed: ${err.message}`);
|
||||
});
|
||||
}
|
||||
|
||||
@ -393,7 +395,7 @@ class Client extends BaseClient {
|
||||
/**
|
||||
* Sign in with the QR code on your phone.
|
||||
* @param {boolean} debug Debug mode
|
||||
* @returns {DiscordAuthWebsocket} nothing :))
|
||||
* @returns {DiscordAuthWebsocket}
|
||||
* @example
|
||||
* client.QRLogin();
|
||||
*/
|
||||
|
@ -10,7 +10,7 @@ class WebhooksUpdate extends Action {
|
||||
/**
|
||||
* Emitted whenever a channel has its webhooks changed.
|
||||
* @event Client#webhookUpdate
|
||||
* @param {TextChannel|NewsChannel|VoiceChannel} channel The channel that had a webhook update
|
||||
* @param {TextChannel|NewsChannel|VoiceChannel|ForumChannel} channel The channel that had a webhook update
|
||||
*/
|
||||
if (channel) client.emit(Events.WEBHOOKS_UPDATE, channel);
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ const { Events } = require('../../../util/Constants');
|
||||
|
||||
/**
|
||||
* @typedef {Object} InteractionResponseBody
|
||||
* @property {Snowflake} id maybe id of this event (???) (documentation needed)
|
||||
* @property {Snowflake} id Interaction ID
|
||||
* @property {Snowflake} nonce nonce in POST /interactions
|
||||
*/
|
||||
|
||||
|
@ -12,4 +12,6 @@ module.exports = (client, { d: data }) => {
|
||||
status: false,
|
||||
metadata: data,
|
||||
});
|
||||
// Delete cache
|
||||
client._interactionCache.delete(data.nonce);
|
||||
};
|
||||
|
@ -8,8 +8,21 @@ module.exports = (client, { d: data }) => {
|
||||
* @param {InteractionResponseBody} data data
|
||||
*/
|
||||
client.emit(Events.INTERACTION_SUCCESS, data);
|
||||
// Get channel data
|
||||
const cache = client._interactionCache.get(data.nonce);
|
||||
const channel = cache.guildId
|
||||
? client.guilds.cache.get(cache.guildId)?.channels.cache.get(cache.channelId)
|
||||
: client.channels.cache.get(cache.channelId);
|
||||
// Set data
|
||||
const interaction = {
|
||||
...cache,
|
||||
...data,
|
||||
};
|
||||
const data_ = channel.interactions._add(interaction);
|
||||
client.emit('interactionResponse', {
|
||||
status: true,
|
||||
metadata: data,
|
||||
metadata: data_,
|
||||
});
|
||||
// Delete cache
|
||||
// client._interactionCache.delete(data.nonce);
|
||||
};
|
||||
|
@ -1,12 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
let ClientUser;
|
||||
const { VoiceConnection, VoiceConnectionStatus } = require('@discordjs/voice');
|
||||
const { VoiceConnection } = require('@discordjs/voice');
|
||||
const axios = require('axios');
|
||||
const chalk = require('chalk');
|
||||
const Discord = require('../../../index');
|
||||
const { Events, Opcodes } = require('../../../util/Constants');
|
||||
const { Networking } = require('../../../util/Voice');
|
||||
const { VoiceConnection: VoiceConnection_patch } = require('../../../util/Voice');
|
||||
let running = false;
|
||||
/**
|
||||
* Emitted whenever clientOptions.checkUpdate = false
|
||||
@ -66,35 +66,7 @@ module.exports = async (client, { d: data }, shard) => {
|
||||
|
||||
if (client.options.patchVoice && !running) {
|
||||
/* eslint-disable */
|
||||
VoiceConnection.prototype.configureNetworking = function () {
|
||||
const { server, state } = this.packets;
|
||||
if (
|
||||
!server ||
|
||||
!state ||
|
||||
this.state.status === VoiceConnectionStatus.Destroyed /* Destroyed */ ||
|
||||
!server.endpoint
|
||||
)
|
||||
return;
|
||||
const networking = new Networking(
|
||||
{
|
||||
endpoint: server.endpoint,
|
||||
serverId: server.guild_id ?? server.channel_id,
|
||||
token: server.token,
|
||||
sessionId: state.session_id,
|
||||
userId: state.user_id,
|
||||
},
|
||||
Boolean(this.debug),
|
||||
);
|
||||
networking.once('close', this.onNetworkingClose);
|
||||
networking.on('stateChange', this.onNetworkingStateChange);
|
||||
networking.on('error', this.onNetworkingError);
|
||||
networking.on('debug', this.onNetworkingDebug);
|
||||
this.state = {
|
||||
...this.state,
|
||||
status: VoiceConnectionStatus.Connecting /* Connecting */,
|
||||
networking,
|
||||
};
|
||||
};
|
||||
VoiceConnection.prototype.configureNetworking = VoiceConnection_patch.prototype.configureNetworking;
|
||||
client.emit(
|
||||
Events.DEBUG,
|
||||
`${chalk.greenBright('[OK]')} Patched ${chalk.cyanBright(
|
||||
|
@ -207,6 +207,8 @@ const Messages = {
|
||||
NITRO_REQUIRED: 'This feature is only available for Nitro users.',
|
||||
NITRO_BOOST_REQUIRED: feature => `This feature (${feature}) is only available for Nitro Boost users.`,
|
||||
ONLY_ME: 'This feature is only available for self.',
|
||||
|
||||
GUILD_FORUM_MESSAGE_REQUIRED: 'You must provide a message to create a guild forum thread',
|
||||
};
|
||||
|
||||
for (const [name, message] of Object.entries(Messages)) register(name, message);
|
||||
|
@ -9,10 +9,11 @@ const GuildChannel = require('../structures/GuildChannel');
|
||||
const PermissionOverwrites = require('../structures/PermissionOverwrites');
|
||||
const ThreadChannel = require('../structures/ThreadChannel');
|
||||
const Webhook = require('../structures/Webhook');
|
||||
const { ThreadChannelTypes, ChannelTypes, VideoQualityModes } = require('../util/Constants');
|
||||
const ChannelFlags = require('../util/ChannelFlags');
|
||||
const { ThreadChannelTypes, ChannelTypes, VideoQualityModes, SortOrderTypes } = require('../util/Constants');
|
||||
const DataResolver = require('../util/DataResolver');
|
||||
const Util = require('../util/Util');
|
||||
const { resolveAutoArchiveMaxLimit } = require('../util/Util');
|
||||
const { resolveAutoArchiveMaxLimit, transformGuildForumTag, transformGuildDefaultReaction } = require('../util/Util');
|
||||
|
||||
let cacheWarningEmitted = false;
|
||||
let storeChannelDeprecationEmitted = false;
|
||||
@ -73,8 +74,9 @@ class GuildChannelManager extends CachedManager {
|
||||
* Data that can be resolved to give a Guild Channel object. This can be:
|
||||
* * A GuildChannel object
|
||||
* * A ThreadChannel object
|
||||
* * A ForumChannel object
|
||||
* * A Snowflake
|
||||
* @typedef {GuildChannel|ThreadChannel|Snowflake} GuildChannelResolvable
|
||||
* @typedef {GuildChannel|ThreadChannel|ForumChannel|Snowflake} GuildChannelResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -138,6 +140,10 @@ class GuildChannelManager extends CachedManager {
|
||||
position,
|
||||
rateLimitPerUser,
|
||||
rtcRegion,
|
||||
videoQualityMode,
|
||||
availableTags,
|
||||
defaultReactionEmoji,
|
||||
defaultSortOrder,
|
||||
reason,
|
||||
} = {},
|
||||
) {
|
||||
@ -145,6 +151,10 @@ class GuildChannelManager extends CachedManager {
|
||||
permissionOverwrites &&= permissionOverwrites.map(o => PermissionOverwrites.resolve(o, this.guild));
|
||||
const intType = typeof type === 'number' ? type : ChannelTypes[type] ?? ChannelTypes.GUILD_TEXT;
|
||||
|
||||
const videoMode = typeof videoQualityMode === 'number' ? videoQualityMode : VideoQualityModes[videoQualityMode];
|
||||
|
||||
const sortMode = typeof defaultSortOrder === 'number' ? defaultSortOrder : SortOrderTypes[defaultSortOrder];
|
||||
|
||||
if (intType === ChannelTypes.GUILD_STORE && !storeChannelDeprecationEmitted) {
|
||||
storeChannelDeprecationEmitted = true;
|
||||
process.emitWarning(
|
||||
@ -167,6 +177,10 @@ class GuildChannelManager extends CachedManager {
|
||||
permission_overwrites: permissionOverwrites,
|
||||
rate_limit_per_user: rateLimitPerUser,
|
||||
rtc_region: rtcRegion,
|
||||
video_quality_mode: videoMode,
|
||||
available_tags: availableTags?.map(availableTag => transformGuildForumTag(availableTag)),
|
||||
default_reaction_emoji: defaultReactionEmoji && transformGuildDefaultReaction(defaultReactionEmoji),
|
||||
default_sort_order: sortMode,
|
||||
},
|
||||
reason,
|
||||
});
|
||||
@ -224,6 +238,11 @@ class GuildChannelManager extends CachedManager {
|
||||
* The default auto archive duration for all new threads in this channel
|
||||
* @property {?string} [rtcRegion] The RTC region of the channel
|
||||
* @property {?VideoQualityMode|number} [videoQualityMode] The camera video quality mode of the channel
|
||||
* @property {ChannelFlagsResolvable} [flags] The flags to set on the channel
|
||||
* @property {GuildForumTagData[]} [availableTags] The tags to set as available in a forum channel
|
||||
* @property {?DefaultReactionEmoji} [defaultReactionEmoji] The emoji to set as the default reaction emoji
|
||||
* @property {number} [defaultThreadRateLimitPerUser] The rate limit per user (slowmode) to set on forum posts
|
||||
* @property {?SortOrderType} [defaultSortOrder] The default sort order mode to set on the channel
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -282,6 +301,12 @@ class GuildChannelManager extends CachedManager {
|
||||
rate_limit_per_user: data.rateLimitPerUser,
|
||||
default_auto_archive_duration: defaultAutoArchiveDuration,
|
||||
permission_overwrites,
|
||||
available_tags: data.availableTags?.map(availableTag => transformGuildForumTag(availableTag)),
|
||||
default_reaction_emoji: data.defaultReactionEmoji && transformGuildDefaultReaction(data.defaultReactionEmoji),
|
||||
default_thread_rate_limit_per_user: data.defaultThreadRateLimitPerUser,
|
||||
flags: 'flags' in data ? ChannelFlags.resolve(data.flags) : undefined,
|
||||
default_sort_order:
|
||||
typeof data.defaultSortOrder === 'string' ? SortOrderTypes[data.defaultSortOrder] : data.defaultSortOrder,
|
||||
},
|
||||
reason,
|
||||
});
|
||||
|
92
src/managers/GuildForumThreadManager.js
Normal file
92
src/managers/GuildForumThreadManager.js
Normal file
@ -0,0 +1,92 @@
|
||||
'use strict';
|
||||
|
||||
const ThreadManager = require('./ThreadManager');
|
||||
const { TypeError } = require('../errors');
|
||||
const MessagePayload = require('../structures/MessagePayload');
|
||||
const { resolveAutoArchiveMaxLimit } = require('../util/Util');
|
||||
|
||||
/**
|
||||
* Manages API methods for threads in forum channels and stores their cache.
|
||||
* @extends {ThreadManager}
|
||||
*/
|
||||
class GuildForumThreadManager extends ThreadManager {
|
||||
/**
|
||||
* The channel this Manager belongs to
|
||||
* @name GuildForumThreadManager#channel
|
||||
* @type {ForumChannel}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {BaseMessageOptions} GuildForumThreadMessageCreateOptions
|
||||
* @property {stickers} [stickers] The stickers to send with the message
|
||||
* @property {BitFieldResolvable} [flags] The flags to send with the message
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for creating a thread.
|
||||
* @typedef {StartThreadOptions} GuildForumThreadCreateOptions
|
||||
* @property {GuildForumThreadMessageCreateOptions|MessagePayload} message The message associated with the thread post
|
||||
* @property {Snowflake[]} [appliedTags] The tags to apply to the thread
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a new thread in the channel.
|
||||
* @param {GuildForumThreadCreateOptions} [options] Options to create a new thread
|
||||
* @returns {Promise<ThreadChannel>}
|
||||
* @example
|
||||
* // Create a new forum post
|
||||
* forum.threads
|
||||
* .create({
|
||||
* name: 'Food Talk',
|
||||
* autoArchiveDuration: 60,
|
||||
* message: {
|
||||
* content: 'Discuss your favorite food!',
|
||||
* },
|
||||
* reason: 'Needed a separate thread for food',
|
||||
* })
|
||||
* .then(threadChannel => console.log(threadChannel))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async create({
|
||||
name,
|
||||
autoArchiveDuration = this.channel.defaultAutoArchiveDuration,
|
||||
message,
|
||||
reason,
|
||||
rateLimitPerUser,
|
||||
appliedTags,
|
||||
} = {}) {
|
||||
let path = this.client.api.channels(this.channel.id);
|
||||
|
||||
if (!message) {
|
||||
throw new TypeError('GUILD_FORUM_MESSAGE_REQUIRED');
|
||||
}
|
||||
|
||||
let messagePayload;
|
||||
|
||||
if (message instanceof MessagePayload) {
|
||||
messagePayload = message.resolveData();
|
||||
} else {
|
||||
messagePayload = MessagePayload.create(this, message).resolveData();
|
||||
}
|
||||
|
||||
const { data: body, files } = await messagePayload.resolveFiles();
|
||||
|
||||
if (autoArchiveDuration === 'MAX') autoArchiveDuration = resolveAutoArchiveMaxLimit(this.channel.guild);
|
||||
|
||||
const data = await path.threads.post({
|
||||
data: {
|
||||
name,
|
||||
auto_archive_duration: autoArchiveDuration,
|
||||
rate_limit_per_user: rateLimitPerUser,
|
||||
applied_tags: appliedTags,
|
||||
message: body,
|
||||
},
|
||||
files,
|
||||
reason,
|
||||
});
|
||||
|
||||
return this.client.actions.ThreadCreate.handle(data).thread;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GuildForumThreadManager;
|
98
src/managers/GuildTextThreadManager.js
Normal file
98
src/managers/GuildTextThreadManager.js
Normal file
@ -0,0 +1,98 @@
|
||||
'use strict';
|
||||
|
||||
const ThreadManager = require('./ThreadManager');
|
||||
const { TypeError } = require('../errors');
|
||||
const { ChannelTypes } = require('../util/Constants');
|
||||
const { resolveAutoArchiveMaxLimit } = require('../util/Util');
|
||||
|
||||
/**
|
||||
* Manages API methods for {@link ThreadChannel} objects and stores their cache.
|
||||
* @extends {ThreadManager}
|
||||
*/
|
||||
class GuildTextThreadManager extends ThreadManager {
|
||||
/**
|
||||
* The channel this Manager belongs to
|
||||
* @name GuildTextThreadManager#channel
|
||||
* @type {TextChannel|NewsChannel}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for creating a thread. <warn>Only one of `startMessage` or `type` can be defined.</warn>
|
||||
* @typedef {StartThreadOptions} GuildTextThreadCreateOptions
|
||||
* @property {MessageResolvable} [startMessage] The message to start a thread from. <warn>If this is defined then type
|
||||
* of thread gets automatically defined and cannot be changed. The provided `type` field will be ignored</warn>
|
||||
* @property {ThreadChannelTypes|number} [type] The type of thread to create. Defaults to `GUILD_PUBLIC_THREAD` if
|
||||
* created in a {@link TextChannel} <warn>When creating threads in a {@link NewsChannel} this is ignored and is always
|
||||
* `GUILD_NEWS_THREAD`</warn>
|
||||
* @property {boolean} [invitable] Whether non-moderators can add other non-moderators to the thread
|
||||
* <info>Can only be set when type will be `GUILD_PRIVATE_THREAD`</info>
|
||||
* @property {number} [rateLimitPerUser] The rate limit per user (slowmode) for the new channel in seconds
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a new thread in the channel.
|
||||
* @param {GuildTextThreadCreateOptions} [options] Options to create a new thread
|
||||
* @returns {Promise<ThreadChannel>}
|
||||
* @example
|
||||
* // Create a new public thread
|
||||
* channel.threads
|
||||
* .create({
|
||||
* name: 'food-talk',
|
||||
* autoArchiveDuration: 60,
|
||||
* reason: 'Needed a separate thread for food',
|
||||
* })
|
||||
* .then(threadChannel => console.log(threadChannel))
|
||||
* .catch(console.error);
|
||||
* @example
|
||||
* // Create a new private thread
|
||||
* channel.threads
|
||||
* .create({
|
||||
* name: 'mod-talk',
|
||||
* autoArchiveDuration: 60,
|
||||
* type: 'GUILD_PRIVATE_THREAD',
|
||||
* reason: 'Needed a separate thread for moderation',
|
||||
* })
|
||||
* .then(threadChannel => console.log(threadChannel))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async create({
|
||||
name,
|
||||
autoArchiveDuration = this.channel.defaultAutoArchiveDuration,
|
||||
startMessage,
|
||||
type,
|
||||
invitable,
|
||||
reason,
|
||||
rateLimitPerUser,
|
||||
} = {}) {
|
||||
let path = this.client.api.channels(this.channel.id);
|
||||
if (type && typeof type !== 'string' && typeof type !== 'number') {
|
||||
throw new TypeError('INVALID_TYPE', 'type', 'ThreadChannelType or Number');
|
||||
}
|
||||
let resolvedType =
|
||||
this.channel.type === 'GUILD_NEWS' ? ChannelTypes.GUILD_NEWS_THREAD : ChannelTypes.GUILD_PUBLIC_THREAD;
|
||||
if (startMessage) {
|
||||
const startMessageId = this.channel.messages.resolveId(startMessage);
|
||||
if (!startMessageId) throw new TypeError('INVALID_TYPE', 'startMessage', 'MessageResolvable');
|
||||
path = path.messages(startMessageId);
|
||||
} else if (this.channel.type !== 'GUILD_NEWS') {
|
||||
resolvedType = typeof type === 'string' ? ChannelTypes[type] : type ?? resolvedType;
|
||||
}
|
||||
|
||||
if (autoArchiveDuration === 'MAX') autoArchiveDuration = resolveAutoArchiveMaxLimit(this.channel.guild);
|
||||
|
||||
const data = await path.threads.post({
|
||||
data: {
|
||||
name,
|
||||
auto_archive_duration: autoArchiveDuration,
|
||||
type: resolvedType,
|
||||
invitable: resolvedType === ChannelTypes.GUILD_PRIVATE_THREAD ? invitable : undefined,
|
||||
rate_limit_per_user: rateLimitPerUser,
|
||||
},
|
||||
reason,
|
||||
});
|
||||
|
||||
return this.client.actions.ThreadCreate.handle(data).thread;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GuildTextThreadManager;
|
39
src/managers/InteractionManager.js
Normal file
39
src/managers/InteractionManager.js
Normal file
@ -0,0 +1,39 @@
|
||||
'use strict';
|
||||
|
||||
const CachedManager = require('./CachedManager');
|
||||
const InteractionResponse = require('../structures/InteractionResponse');
|
||||
|
||||
/**
|
||||
* Manages API methods for InteractionResponse and holds their cache.
|
||||
* @extends {CachedManager}
|
||||
*/
|
||||
class InteractionManager extends CachedManager {
|
||||
constructor(channel, iterable) {
|
||||
super(channel.client, InteractionResponse, iterable);
|
||||
|
||||
/**
|
||||
* The channel that the messages belong to
|
||||
* @type {TextBasedChannels}
|
||||
*/
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* The cache of InteractionResponse
|
||||
* @type {Collection<Snowflake, InteractionResponse>}
|
||||
* @name InteractionManager#cache
|
||||
*/
|
||||
|
||||
_add(data, cache) {
|
||||
data = {
|
||||
...data,
|
||||
channelId: this.channel.id,
|
||||
guildId: this.channel.guild?.id,
|
||||
};
|
||||
if (!data.id) return;
|
||||
// eslint-disable-next-line consistent-return
|
||||
return super._add(data, cache);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = InteractionManager;
|
@ -17,12 +17,12 @@ class SessionManager extends CachedManager {
|
||||
*/
|
||||
fetch() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.api.auth.session
|
||||
this.client.api.auth.sessions
|
||||
.get()
|
||||
.then(data => {
|
||||
const allData = data.user_sessions;
|
||||
for (const session of allData) {
|
||||
this._add(new Session(this.client, session), true);
|
||||
this._add(new Session(this.client, session), true, { id: session.id_hash });
|
||||
}
|
||||
resolve(this);
|
||||
})
|
||||
|
@ -4,8 +4,6 @@ const { Collection } = require('@discordjs/collection');
|
||||
const CachedManager = require('./CachedManager');
|
||||
const { TypeError } = require('../errors');
|
||||
const ThreadChannel = require('../structures/ThreadChannel');
|
||||
const { ChannelTypes } = require('../util/Constants');
|
||||
const { resolveAutoArchiveMaxLimit } = require('../util/Util');
|
||||
|
||||
/**
|
||||
* Manages API methods for {@link ThreadChannel} objects and stores their cache.
|
||||
@ -60,84 +58,6 @@ class ThreadManager extends CachedManager {
|
||||
* @returns {?Snowflake}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for creating a thread. <warn>Only one of `startMessage` or `type` can be defined.</warn>
|
||||
* @typedef {StartThreadOptions} ThreadCreateOptions
|
||||
* @property {MessageResolvable} [startMessage] The message to start a thread from. <warn>If this is defined then type
|
||||
* of thread gets automatically defined and cannot be changed. The provided `type` field will be ignored</warn>
|
||||
* @property {ThreadChannelTypes|number} [type] The type of thread to create. Defaults to `GUILD_PUBLIC_THREAD` if
|
||||
* created in a {@link TextChannel} <warn>When creating threads in a {@link NewsChannel} this is ignored and is always
|
||||
* `GUILD_NEWS_THREAD`</warn>
|
||||
* @property {boolean} [invitable] Whether non-moderators can add other non-moderators to the thread
|
||||
* <info>Can only be set when type will be `GUILD_PRIVATE_THREAD`</info>
|
||||
* @property {number} [rateLimitPerUser] The rate limit per user (slowmode) for the new channel in seconds
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a new thread in the channel.
|
||||
* @param {ThreadCreateOptions} [options] Options to create a new thread
|
||||
* @returns {Promise<ThreadChannel>}
|
||||
* @example
|
||||
* // Create a new public thread
|
||||
* channel.threads
|
||||
* .create({
|
||||
* name: 'food-talk',
|
||||
* autoArchiveDuration: 60,
|
||||
* reason: 'Needed a separate thread for food',
|
||||
* })
|
||||
* .then(threadChannel => console.log(threadChannel))
|
||||
* .catch(console.error);
|
||||
* @example
|
||||
* // Create a new private thread
|
||||
* channel.threads
|
||||
* .create({
|
||||
* name: 'mod-talk',
|
||||
* autoArchiveDuration: 60,
|
||||
* type: 'GUILD_PRIVATE_THREAD',
|
||||
* reason: 'Needed a separate thread for moderation',
|
||||
* })
|
||||
* .then(threadChannel => console.log(threadChannel))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async create({
|
||||
name,
|
||||
autoArchiveDuration = this.channel.defaultAutoArchiveDuration,
|
||||
startMessage,
|
||||
type,
|
||||
invitable,
|
||||
reason,
|
||||
rateLimitPerUser,
|
||||
} = {}) {
|
||||
let path = this.client.api.channels(this.channel.id);
|
||||
if (type && typeof type !== 'string' && typeof type !== 'number') {
|
||||
throw new TypeError('INVALID_TYPE', 'type', 'ThreadChannelType or Number');
|
||||
}
|
||||
let resolvedType =
|
||||
this.channel.type === 'GUILD_NEWS' ? ChannelTypes.GUILD_NEWS_THREAD : ChannelTypes.GUILD_PUBLIC_THREAD;
|
||||
if (startMessage) {
|
||||
const startMessageId = this.channel.messages.resolveId(startMessage);
|
||||
if (!startMessageId) throw new TypeError('INVALID_TYPE', 'startMessage', 'MessageResolvable');
|
||||
path = path.messages(startMessageId);
|
||||
} else if (this.channel.type !== 'GUILD_NEWS') {
|
||||
resolvedType = typeof type === 'string' ? ChannelTypes[type] : type ?? resolvedType;
|
||||
}
|
||||
|
||||
if (autoArchiveDuration === 'MAX') autoArchiveDuration = resolveAutoArchiveMaxLimit(this.channel.guild);
|
||||
|
||||
const data = await path.threads.post({
|
||||
data: {
|
||||
name,
|
||||
auto_archive_duration: autoArchiveDuration,
|
||||
type: resolvedType,
|
||||
invitable: resolvedType === ChannelTypes.GUILD_PRIVATE_THREAD ? invitable : undefined,
|
||||
rate_limit_per_user: rateLimitPerUser,
|
||||
},
|
||||
reason,
|
||||
});
|
||||
|
||||
return this.client.actions.ThreadCreate.handle(data).thread;
|
||||
}
|
||||
|
||||
/**
|
||||
* The options for fetching multiple threads, the properties are mutually exclusive
|
||||
* @typedef {Object} FetchThreadsOptions
|
||||
|
@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const { setTimeout } = require('node:timers');
|
||||
const { findBestMatch } = require('string-similarity');
|
||||
const Base = require('./Base');
|
||||
const ApplicationCommandPermissionsManager = require('../managers/ApplicationCommandPermissionsManager');
|
||||
const MessageAttachment = require('../structures/MessageAttachment');
|
||||
@ -596,7 +597,7 @@ class ApplicationCommand extends Base {
|
||||
* @param {Message} message Discord Message
|
||||
* @param {Array<string>} subCommandArray SubCommand Array
|
||||
* @param {Array<string>} options The options to Slash Command
|
||||
* @returns {Promise<InteractionResponseBody>}
|
||||
* @returns {Promise<InteractionResponse>}
|
||||
*/
|
||||
// eslint-disable-next-line consistent-return
|
||||
async sendSlashCommand(message, subCommandArray = [], options = []) {
|
||||
@ -696,7 +697,9 @@ class ApplicationCommand extends Base {
|
||||
const subGroup = this.options.find(
|
||||
o => o.name == subCommandArray[0] && o.type == 'SUB_COMMAND_GROUP',
|
||||
);
|
||||
const subCommand = this.options.find(o => o.name == subCommandArray[1] && o.type == 'SUB_COMMAND');
|
||||
const subCommand = subGroup.options.find(
|
||||
o => o.name == subCommandArray[1] && o.type == 'SUB_COMMAND',
|
||||
);
|
||||
optionsBuild = [
|
||||
{
|
||||
type: ApplicationCommandOptionTypes[subGroup.type],
|
||||
@ -830,8 +833,17 @@ class ApplicationCommand extends Base {
|
||||
clearTimeout(timeout);
|
||||
this.client.removeListener(Events.APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE, handler);
|
||||
this.client.decrementMaxListeners();
|
||||
if (data.choices.length > 1) resolve(data.choices[0].value);
|
||||
else resolve(value);
|
||||
if (data.choices.length > 1) {
|
||||
// Find best match name
|
||||
const bestMatch = findBestMatch(
|
||||
value,
|
||||
data.choices.map(c => c.name),
|
||||
);
|
||||
const result = data.choices.find(c => c.name == bestMatch.bestMatch.target);
|
||||
resolve(result.value);
|
||||
} else {
|
||||
resolve(value);
|
||||
}
|
||||
};
|
||||
const timeout = setTimeout(() => {
|
||||
this.client.removeListener(Events.APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE, handler);
|
||||
@ -849,6 +861,11 @@ class ApplicationCommand extends Base {
|
||||
body: data,
|
||||
files: attachmentsBuffer,
|
||||
});
|
||||
this.client._interactionCache.set(nonce, {
|
||||
channelId: message.channelId,
|
||||
guildId: message.guildId,
|
||||
metadata: data,
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
const handler = data => {
|
||||
timeout.refresh();
|
||||
@ -899,7 +916,7 @@ class ApplicationCommand extends Base {
|
||||
/**
|
||||
* Message Context Menu
|
||||
* @param {Message} message Discord Message
|
||||
* @returns {Promise<InteractionResponseBody>}
|
||||
* @returns {Promise<InteractionResponse>}
|
||||
*/
|
||||
async sendContextMenu(message) {
|
||||
if (!(message instanceof Message())) {
|
||||
@ -929,6 +946,11 @@ class ApplicationCommand extends Base {
|
||||
await this.client.api.interactions.post({
|
||||
body: data,
|
||||
});
|
||||
this.client._interactionCache.set(nonce, {
|
||||
channelId: message.channelId,
|
||||
guildId: message.guildId,
|
||||
metadata: data,
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
const handler = data => {
|
||||
timeout.refresh();
|
||||
|
@ -2,8 +2,9 @@
|
||||
|
||||
const GuildChannel = require('./GuildChannel');
|
||||
const TextBasedChannel = require('./interfaces/TextBasedChannel');
|
||||
const GuildTextThreadManager = require('../managers/GuildTextThreadManager');
|
||||
const InteractionManager = require('../managers/InteractionManager');
|
||||
const MessageManager = require('../managers/MessageManager');
|
||||
const ThreadManager = require('../managers/ThreadManager');
|
||||
|
||||
/**
|
||||
* Represents a text-based guild channel on Discord.
|
||||
@ -21,10 +22,16 @@ class BaseGuildTextChannel extends GuildChannel {
|
||||
this.messages = new MessageManager(this);
|
||||
|
||||
/**
|
||||
* A manager of the threads belonging to this channel
|
||||
* @type {ThreadManager}
|
||||
* A manager of the interactions sent to this channel
|
||||
* @type {InteractionManager}
|
||||
*/
|
||||
this.threads = new ThreadManager(this);
|
||||
this.interactions = new InteractionManager(this);
|
||||
|
||||
/**
|
||||
* A manager of the threads belonging to this channel
|
||||
* @type {GuildTextThreadManager}
|
||||
*/
|
||||
this.threads = new GuildTextThreadManager(this);
|
||||
|
||||
/**
|
||||
* If the guild considers this channel NSFW
|
||||
|
@ -40,6 +40,11 @@ class CategoryChannel extends GuildChannel {
|
||||
* @property {number} [position] Position of the new channel
|
||||
* @property {number} [rateLimitPerUser] The rate limit per user (slowmode) for the new channel in seconds
|
||||
* @property {string} [rtcRegion] The specific region of the new channel.
|
||||
* @property {VideoQualityMode} [videoQualityMode] The camera video quality mode of the voice channel
|
||||
* @property {GuildForumTagData[]} [availableTags] The tags that can be used in this channel (forum only).
|
||||
* @property {DefaultReactionEmoji} [defaultReactionEmoji]
|
||||
* The emoji to show in the add reaction button on a thread in a guild forum channel.
|
||||
* @property {SortOrderType} [defaultSortOrder] The default sort order mode used to order posts (forum only).
|
||||
* @property {string} [reason] Reason for creating the new channel
|
||||
*/
|
||||
|
||||
|
@ -11,6 +11,8 @@ let TextChannel;
|
||||
let ThreadChannel;
|
||||
let VoiceChannel;
|
||||
let DirectoryChannel;
|
||||
let ForumChannel;
|
||||
const ChannelFlags = require('../util/ChannelFlags');
|
||||
const { ChannelTypes, ThreadChannelTypes, VoiceBasedChannelTypes } = require('../util/Constants');
|
||||
const SnowflakeUtil = require('../util/SnowflakeUtil');
|
||||
// Const { ApplicationCommand } = require('discord.js-selfbot-v13'); - Not being used in this file, not necessary.
|
||||
@ -48,6 +50,17 @@ class Channel extends Base {
|
||||
* @type {Snowflake}
|
||||
*/
|
||||
this.id = data.id;
|
||||
|
||||
if ('flags' in data) {
|
||||
/**
|
||||
* The flags that are applied to the channel.
|
||||
* <info>This is only `null` in a {@link PartialGroupDMChannel}. In all other cases, it is not `null`.</info>
|
||||
* @type {?Readonly<ChannelFlags>}
|
||||
*/
|
||||
this.flags = new ChannelFlags(data.flags).freeze();
|
||||
} else {
|
||||
this.flags ??= new ChannelFlags().freeze();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -184,6 +197,7 @@ class Channel extends Base {
|
||||
ThreadChannel ??= require('./ThreadChannel');
|
||||
VoiceChannel ??= require('./VoiceChannel');
|
||||
DirectoryChannel ??= require('./DirectoryChannel');
|
||||
ForumChannel ??= require('./ForumChannel');
|
||||
|
||||
let channel;
|
||||
if (!data.guild_id && !guild) {
|
||||
@ -232,6 +246,9 @@ class Channel extends Base {
|
||||
case ChannelTypes.GUILD_DIRECTORY:
|
||||
channel = new DirectoryChannel(client, data);
|
||||
break;
|
||||
case ChannelTypes.GUILD_FORUM:
|
||||
channel = new ForumChannel(guild, data, client);
|
||||
break;
|
||||
}
|
||||
if (channel && !allowUnknownGuild) guild.channels?.cache.set(channel.id, channel);
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ const { Collection } = require('@discordjs/collection');
|
||||
const { joinVoiceChannel, entersState, VoiceConnectionStatus } = require('@discordjs/voice');
|
||||
const { Channel } = require('./Channel');
|
||||
const TextBasedChannel = require('./interfaces/TextBasedChannel');
|
||||
const InteractionManager = require('../managers/InteractionManager');
|
||||
const MessageManager = require('../managers/MessageManager');
|
||||
const { Status, Opcodes } = require('../util/Constants');
|
||||
|
||||
@ -24,6 +25,12 @@ class DMChannel extends Channel {
|
||||
* @type {MessageManager}
|
||||
*/
|
||||
this.messages = new MessageManager(this);
|
||||
|
||||
/**
|
||||
* A manager of the interactions sent to this channel
|
||||
* @type {InteractionManager}
|
||||
*/
|
||||
this.interactions = new InteractionManager(this);
|
||||
}
|
||||
|
||||
_patch(data) {
|
||||
|
249
src/structures/ForumChannel.js
Normal file
249
src/structures/ForumChannel.js
Normal file
@ -0,0 +1,249 @@
|
||||
'use strict';
|
||||
|
||||
const GuildChannel = require('./GuildChannel');
|
||||
const TextBasedChannel = require('./interfaces/TextBasedChannel');
|
||||
const GuildForumThreadManager = require('../managers/GuildForumThreadManager');
|
||||
const { SortOrderTypes } = require('../util/Constants');
|
||||
const { transformAPIGuildForumTag, transformAPIGuildDefaultReaction } = require('../util/Util');
|
||||
|
||||
/**
|
||||
* @typedef {Object} GuildForumTagEmoji
|
||||
* @property {?Snowflake} id The id of a guild's custom emoji
|
||||
* @property {?string} name The unicode character of the emoji
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} GuildForumTag
|
||||
* @property {Snowflake} id The id of the tag
|
||||
* @property {string} name The name of the tag
|
||||
* @property {boolean} moderated Whether this tag can only be added to or removed from threads
|
||||
* by a member with the `ManageThreads` permission
|
||||
* @property {?GuildForumTagEmoji} emoji The emoji of this tag
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} GuildForumTagData
|
||||
* @property {Snowflake} [id] The id of the tag
|
||||
* @property {string} name The name of the tag
|
||||
* @property {boolean} [moderated] Whether this tag can only be added to or removed from threads
|
||||
* by a member with the `ManageThreads` permission
|
||||
* @property {?GuildForumTagEmoji} [emoji] The emoji of this tag
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} DefaultReactionEmoji
|
||||
* @property {?Snowflake} id The id of a guild's custom emoji
|
||||
* @property {?string} name The unicode character of the emoji
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a channel that only contains threads
|
||||
* @extends {GuildChannel}
|
||||
* @implements {TextBasedChannel}
|
||||
*/
|
||||
class ForumChannel extends GuildChannel {
|
||||
constructor(guild, data, client) {
|
||||
super(guild, data, client, false);
|
||||
|
||||
/**
|
||||
* A manager of the threads belonging to this channel
|
||||
* @type {GuildForumThreadManager}
|
||||
*/
|
||||
this.threads = new GuildForumThreadManager(this);
|
||||
|
||||
this._patch(data);
|
||||
}
|
||||
|
||||
_patch(data) {
|
||||
super._patch(data);
|
||||
if ('available_tags' in data) {
|
||||
/**
|
||||
* The set of tags that can be used in this channel.
|
||||
* @type {GuildForumTag[]}
|
||||
*/
|
||||
this.availableTags = data.available_tags.map(tag => transformAPIGuildForumTag(tag));
|
||||
} else {
|
||||
this.availableTags ??= [];
|
||||
}
|
||||
|
||||
if ('default_reaction_emoji' in data) {
|
||||
/**
|
||||
* The emoji to show in the add reaction button on a thread in a guild forum channel
|
||||
* @type {?DefaultReactionEmoji}
|
||||
*/
|
||||
this.defaultReactionEmoji = data.default_reaction_emoji
|
||||
? transformAPIGuildDefaultReaction(data.default_reaction_emoji)
|
||||
: null;
|
||||
} else {
|
||||
this.defaultReactionEmoji ??= null;
|
||||
}
|
||||
|
||||
if ('default_thread_rate_limit_per_user' in data) {
|
||||
/**
|
||||
* The initial rate limit per user (slowmode) to set on newly created threads in a channel.
|
||||
* @type {?number}
|
||||
*/
|
||||
this.defaultThreadRateLimitPerUser = data.default_thread_rate_limit_per_user;
|
||||
} else {
|
||||
this.defaultThreadRateLimitPerUser ??= null;
|
||||
}
|
||||
|
||||
if ('rate_limit_per_user' in data) {
|
||||
/**
|
||||
* The rate limit per user (slowmode) for this channel.
|
||||
* @type {?number}
|
||||
*/
|
||||
this.rateLimitPerUser = data.rate_limit_per_user;
|
||||
} else {
|
||||
this.rateLimitPerUser ??= null;
|
||||
}
|
||||
|
||||
if ('default_auto_archive_duration' in data) {
|
||||
/**
|
||||
* The default auto archive duration for newly created threads in this channel.
|
||||
* @type {?ThreadAutoArchiveDuration}
|
||||
*/
|
||||
this.defaultAutoArchiveDuration = data.default_auto_archive_duration;
|
||||
} else {
|
||||
this.defaultAutoArchiveDuration ??= null;
|
||||
}
|
||||
|
||||
if ('nsfw' in data) {
|
||||
/**
|
||||
* If this channel is considered NSFW.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.nsfw = data.nsfw;
|
||||
} else {
|
||||
this.nsfw ??= false;
|
||||
}
|
||||
|
||||
if ('topic' in data) {
|
||||
/**
|
||||
* The topic of this channel.
|
||||
* @type {?string}
|
||||
*/
|
||||
this.topic = data.topic;
|
||||
}
|
||||
|
||||
if ('default_sort_order' in data) {
|
||||
/**
|
||||
* The default sort order mode used to order posts
|
||||
* @type {?SortOrderType}
|
||||
*/
|
||||
this.defaultSortOrder = SortOrderTypes[data.default_sort_order];
|
||||
} else {
|
||||
this.defaultSortOrder ??= null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the available tags for this forum channel
|
||||
* @param {GuildForumTagData[]} availableTags The tags to set as available in this channel
|
||||
* @param {string} [reason] Reason for changing the available tags
|
||||
* @returns {Promise<ForumChannel>}
|
||||
*/
|
||||
setAvailableTags(availableTags, reason) {
|
||||
return this.edit({ availableTags, reason });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default reaction emoji for this channel
|
||||
* @param {?DefaultReactionEmoji} defaultReactionEmoji The emoji to set as the default reaction emoji
|
||||
* @param {string} [reason] Reason for changing the default reaction emoji
|
||||
* @returns {Promise<ForumChannel>}
|
||||
*/
|
||||
setDefaultReactionEmoji(defaultReactionEmoji, reason) {
|
||||
return this.edit({ defaultReactionEmoji, reason });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default rate limit per user (slowmode) for new threads in this channel
|
||||
* @param {number} defaultThreadRateLimitPerUser The rate limit to set on newly created threads in this channel
|
||||
* @param {string} [reason] Reason for changing the default rate limit
|
||||
* @returns {Promise<ForumChannel>}
|
||||
*/
|
||||
setDefaultThreadRateLimitPerUser(defaultThreadRateLimitPerUser, reason) {
|
||||
return this.edit({ defaultThreadRateLimitPerUser, reason });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default sort order mode used to order posts
|
||||
* @param {?SortOrderType} defaultSortOrder The default sort order mode to set on this channel
|
||||
* @param {string} [reason] Reason for changing the default sort order
|
||||
* @returns {Promise<ForumChannel>}
|
||||
*/
|
||||
setDefaultSortOrder(defaultSortOrder, reason) {
|
||||
return this.edit({ defaultSortOrder, reason });
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an invite to this guild channel.
|
||||
* @param {CreateInviteOptions} [options={}] The options for creating the invite
|
||||
* @returns {Promise<Invite>}
|
||||
* @example
|
||||
* // Create an invite to a channel
|
||||
* channel.createInvite()
|
||||
* .then(invite => console.log(`Created an invite with a code of ${invite.code}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
createInvite(options) {
|
||||
return this.guild.invites.create(this.id, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a collection of invites to this guild channel.
|
||||
* Resolves with a collection mapping invites by their codes.
|
||||
* @param {boolean} [cache=true] Whether or not to cache the fetched invites
|
||||
* @returns {Promise<Collection<string, Invite>>}
|
||||
*/
|
||||
fetchInvites(cache = true) {
|
||||
return this.guild.invites.fetch({ channelId: this.id, cache });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default auto archive duration for all newly created threads in this channel.
|
||||
* @param {ThreadAutoArchiveDuration} defaultAutoArchiveDuration The new default auto archive duration
|
||||
* @param {string} [reason] Reason for changing the channel's default auto archive duration
|
||||
* @returns {Promise<ForumChannel>}
|
||||
*/
|
||||
setDefaultAutoArchiveDuration(defaultAutoArchiveDuration, reason) {
|
||||
return this.edit({ defaultAutoArchiveDuration, reason });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new topic for the guild channel.
|
||||
* @param {?string} topic The new topic for the guild channel
|
||||
* @param {string} [reason] Reason for changing the guild channel's topic
|
||||
* @returns {Promise<ForumChannel>}
|
||||
* @example
|
||||
* // Set a new channel topic
|
||||
* channel.setTopic('needs more rate limiting')
|
||||
* .then(newChannel => console.log(`Channel's new topic is ${newChannel.topic}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setTopic(topic, reason) {
|
||||
return this.edit({ topic, reason });
|
||||
}
|
||||
|
||||
// These are here only for documentation purposes - they are implemented by TextBasedChannel
|
||||
/* eslint-disable no-empty-function */
|
||||
createWebhook() {}
|
||||
fetchWebhooks() {}
|
||||
setNSFW() {}
|
||||
setRateLimitPerUser() {}
|
||||
}
|
||||
|
||||
TextBasedChannel.applyToClass(ForumChannel, true, [
|
||||
'send',
|
||||
'lastMessage',
|
||||
'lastPinAt',
|
||||
'bulkDelete',
|
||||
'sendTyping',
|
||||
'createMessageCollector',
|
||||
'awaitMessages',
|
||||
'createMessageComponentCollector',
|
||||
'awaitMessageComponent',
|
||||
]);
|
||||
|
||||
module.exports = ForumChannel;
|
@ -564,7 +564,7 @@ class Guild extends AnonymousGuild {
|
||||
|
||||
/**
|
||||
* Widget channel for this guild
|
||||
* @type {?TextChannel}
|
||||
* @type {?(TextChannel|NewsChannel|VoiceChannel|StageChannel|ForumChannel)}
|
||||
* @readonly
|
||||
*/
|
||||
get widgetChannel() {
|
||||
|
@ -14,6 +14,7 @@ const Permissions = require('../util/Permissions');
|
||||
* - {@link NewsChannel}
|
||||
* - {@link StoreChannel}
|
||||
* - {@link StageChannel}
|
||||
* - {@link ForumChannel}
|
||||
* @extends {Channel}
|
||||
* @abstract
|
||||
*/
|
||||
|
113
src/structures/InteractionResponse.js
Normal file
113
src/structures/InteractionResponse.js
Normal file
@ -0,0 +1,113 @@
|
||||
'use strict';
|
||||
|
||||
const { setTimeout } = require('node:timers');
|
||||
const Base = require('./Base');
|
||||
const { Events } = require('../util/Constants');
|
||||
const SnowflakeUtil = require('../util/SnowflakeUtil');
|
||||
|
||||
/**
|
||||
* Represents a interaction on Discord.
|
||||
* @extends {Base}
|
||||
*/
|
||||
class InteractionResponse extends Base {
|
||||
constructor(client, data) {
|
||||
super(client);
|
||||
/**
|
||||
* The id of the channel the interaction was sent in
|
||||
* @type {Snowflake}
|
||||
*/
|
||||
this.channelId = data.channelId;
|
||||
|
||||
/**
|
||||
* The id of the guild the interaction was sent in, if any
|
||||
* @type {?Snowflake}
|
||||
*/
|
||||
this.guildId = data.guildId ?? this.channel?.guild?.id ?? null;
|
||||
|
||||
/**
|
||||
* The interaction data was sent in
|
||||
* @type {Object}
|
||||
*/
|
||||
this.sendData = data.metadata;
|
||||
this._patch(data);
|
||||
}
|
||||
|
||||
_patch(data) {
|
||||
if ('id' in data) {
|
||||
/**
|
||||
* The interaction response's ID
|
||||
* @type {Snowflake}
|
||||
*/
|
||||
this.id = data.id;
|
||||
}
|
||||
if ('nonce' in data) {
|
||||
/**
|
||||
* The interaction response's nonce
|
||||
* @type {Snowflake}
|
||||
*/
|
||||
this.nonce = data.nonce;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* The timestamp the interaction response was created at
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get createdTimestamp() {
|
||||
return SnowflakeUtil.timestampFrom(this.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* The time the interaction response was created at
|
||||
* @type {Date}
|
||||
* @readonly
|
||||
*/
|
||||
get createdAt() {
|
||||
return new Date(this.createdTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* The channel that the interaction was sent in
|
||||
* @type {TextBasedChannels}
|
||||
* @readonly
|
||||
*/
|
||||
get channel() {
|
||||
return this.client.channels.resolve(this.channelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* The guild the inteaaction was sent in (if in a guild channel)
|
||||
* @type {?Guild}
|
||||
* @readonly
|
||||
*/
|
||||
get guild() {
|
||||
return this.client.guilds.resolve(this.guildId) ?? this.channel?.guild ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Modal send from interaction
|
||||
* @param {?number} time Time to wait for modal (Default: 120000)
|
||||
* @returns {Modal}
|
||||
*/
|
||||
awaitModal(time = 120_000) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const handler = modal => {
|
||||
timeout.refresh();
|
||||
if (modal.nonce != this.nonce || modal.id != this.id) return;
|
||||
clearTimeout(timeout);
|
||||
this.client.removeListener(Events.INTERACTION_MODAL_CREATE, handler);
|
||||
this.client.decrementMaxListeners();
|
||||
resolve(modal);
|
||||
};
|
||||
const timeout = setTimeout(() => {
|
||||
this.client.removeListener(Events.INTERACTION_MODAL_CREATE, handler);
|
||||
this.client.decrementMaxListeners();
|
||||
reject(new Error('MODAL_TIMEOUT'));
|
||||
}, time || 120_000).unref();
|
||||
this.client.incrementMaxListeners();
|
||||
this.client.on(Events.INTERACTION_MODAL_CREATE, handler);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = InteractionResponse;
|
@ -61,6 +61,17 @@ class Message extends Base {
|
||||
*/
|
||||
this.id = data.id;
|
||||
|
||||
if ('position' in data) {
|
||||
/**
|
||||
* A generally increasing integer (there may be gaps or duplicates) that represents
|
||||
* the approximate position of the message in a thread.
|
||||
* @type {?number}
|
||||
*/
|
||||
this.position = data.position;
|
||||
} else {
|
||||
this.position ??= null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The timestamp the message was sent at
|
||||
* @type {number}
|
||||
@ -860,7 +871,7 @@ class Message extends Base {
|
||||
|
||||
/**
|
||||
* Create a new public thread from this message
|
||||
* @see ThreadManager#create
|
||||
* @see GuildTextThreadManager#create
|
||||
* @param {StartThreadOptions} [options] Options for starting a thread on this message
|
||||
* @returns {Promise<ThreadChannel>}
|
||||
*/
|
||||
@ -1032,7 +1043,7 @@ class Message extends Base {
|
||||
/**
|
||||
* Click specific button
|
||||
* @param {MessageButton|string} button Button ID
|
||||
* @returns {Promise<InteractionResponseBody>}
|
||||
* @returns {Promise<InteractionResponse>}
|
||||
*/
|
||||
clickButton(button) {
|
||||
let buttonID;
|
||||
@ -1055,7 +1066,7 @@ class Message extends Base {
|
||||
* Select specific menu or First Menu
|
||||
* @param {string|Array<string>} menuID Select Menu specific id or auto select first Menu
|
||||
* @param {Array<string>} options Menu Options
|
||||
* @returns {Promise<InteractionResponseBody>}
|
||||
* @returns {Promise<InteractionResponse>}
|
||||
*/
|
||||
async selectMenu(menuID, options = []) {
|
||||
if (!this.components[0]) throw new TypeError('MESSAGE_NO_COMPONENTS');
|
||||
@ -1089,7 +1100,7 @@ class Message extends Base {
|
||||
* Send context Menu v2
|
||||
* @param {Snowflake} botId Bot id
|
||||
* @param {string} commandName Command name in Context Menu
|
||||
* @returns {Promise<InteractionResponseBody>}
|
||||
* @returns {Promise<InteractionResponse>}
|
||||
*/
|
||||
async contextMenu(botId, commandName) {
|
||||
if (!botId) throw new Error('Bot ID is required');
|
||||
|
@ -2,11 +2,12 @@
|
||||
|
||||
const { setTimeout } = require('node:timers');
|
||||
const BaseMessageComponent = require('./BaseMessageComponent');
|
||||
const { Message } = require('./Message');
|
||||
const { RangeError } = require('../errors');
|
||||
const { MessageButtonStyles, MessageComponentTypes } = require('../util/Constants');
|
||||
const SnowflakeUtil = require('../util/SnowflakeUtil');
|
||||
const Util = require('../util/Util');
|
||||
const { lazy } = require('../util/Util');
|
||||
const Message = lazy(() => require('../structures/Message').Message);
|
||||
|
||||
/**
|
||||
* Represents a button message component.
|
||||
@ -167,27 +168,33 @@ class MessageButton extends BaseMessageComponent {
|
||||
/**
|
||||
* Click the button
|
||||
* @param {Message} message Discord Message
|
||||
* @returns {Promise<InteractionResponseBody>}
|
||||
* @returns {Promise<InteractionResponse>}
|
||||
*/
|
||||
async click(message) {
|
||||
const nonce = SnowflakeUtil.generate();
|
||||
if (!(message instanceof Message)) throw new Error('[UNKNOWN_MESSAGE] Please pass a valid Message');
|
||||
if (!(message instanceof Message())) throw new Error('[UNKNOWN_MESSAGE] Please pass a valid Message');
|
||||
if (!this.customId || this.style == 5 || this.disabled) return false; // Button URL, Disabled
|
||||
await message.client.api.interactions.post({
|
||||
const data = {
|
||||
type: 3, // ?
|
||||
nonce,
|
||||
guild_id: message.guild?.id ?? null, // In DMs
|
||||
channel_id: message.channel.id,
|
||||
message_id: message.id,
|
||||
application_id: message.applicationId ?? message.author.id,
|
||||
session_id: message.client.session_id,
|
||||
message_flags: message.flags.bitfield,
|
||||
data: {
|
||||
type: 3, // ?
|
||||
nonce,
|
||||
guild_id: message.guild?.id ?? null, // In DMs
|
||||
channel_id: message.channel.id,
|
||||
message_id: message.id,
|
||||
application_id: message.applicationId ?? message.author.id,
|
||||
session_id: message.client.session_id,
|
||||
message_flags: message.flags.bitfield,
|
||||
data: {
|
||||
component_type: 2, // Button
|
||||
custom_id: this.customId,
|
||||
},
|
||||
component_type: 2, // Button
|
||||
custom_id: this.customId,
|
||||
},
|
||||
};
|
||||
await message.client.api.interactions.post({
|
||||
data,
|
||||
});
|
||||
message.client._interactionCache.set(nonce, {
|
||||
channelId: message.channelId,
|
||||
guildId: message.guildId,
|
||||
metadata: data,
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
const handler = data => {
|
||||
|
@ -144,9 +144,11 @@ class MessagePayload {
|
||||
|
||||
let username;
|
||||
let avatarURL;
|
||||
let threadName;
|
||||
if (isWebhook) {
|
||||
username = this.options.username ?? this.target.name;
|
||||
if (this.options.avatarURL) avatarURL = this.options.avatarURL;
|
||||
if (this.options.threadName) threadName = this.options.threadName;
|
||||
}
|
||||
|
||||
let flags;
|
||||
@ -259,6 +261,7 @@ class MessagePayload {
|
||||
message_reference,
|
||||
attachments: this.options.attachments,
|
||||
sticker_ids: this.options.stickers?.map(sticker => sticker.id ?? sticker),
|
||||
thread_name: threadName,
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
@ -215,7 +215,7 @@ class MessageSelectMenu extends BaseMessageComponent {
|
||||
* Mesage select menu
|
||||
* @param {Message} message The message this select menu is for
|
||||
* @param {Array<string>} values The values of the select menu
|
||||
* @returns {Promise<InteractionResponseBody>}
|
||||
* @returns {Promise<InteractionResponse>}
|
||||
*/
|
||||
async select(message, values = []) {
|
||||
// Github copilot is the best :))
|
||||
@ -242,23 +242,29 @@ class MessageSelectMenu extends BaseMessageComponent {
|
||||
);
|
||||
}
|
||||
const nonce = SnowflakeUtil.generate();
|
||||
await message.client.api.interactions.post({
|
||||
const data = {
|
||||
type: 3, // ?
|
||||
guild_id: message.guild?.id ?? null, // In DMs
|
||||
channel_id: message.channel.id,
|
||||
message_id: message.id,
|
||||
application_id: message.applicationId ?? message.author.id,
|
||||
session_id: message.client.session_id,
|
||||
message_flags: message.flags.bitfield,
|
||||
data: {
|
||||
type: 3, // ?
|
||||
guild_id: message.guild?.id ?? null, // In DMs
|
||||
channel_id: message.channel.id,
|
||||
message_id: message.id,
|
||||
application_id: message.applicationId ?? message.author.id,
|
||||
session_id: message.client.session_id,
|
||||
message_flags: message.flags.bitfield,
|
||||
data: {
|
||||
component_type: 3, // Select Menu
|
||||
custom_id: this.customId,
|
||||
type: 3, // Select Menu
|
||||
values,
|
||||
},
|
||||
nonce,
|
||||
component_type: 3, // Select Menu
|
||||
custom_id: this.customId,
|
||||
type: 3, // Select Menu
|
||||
values,
|
||||
},
|
||||
nonce,
|
||||
};
|
||||
await message.client.api.interactions.post({
|
||||
data,
|
||||
});
|
||||
message.client._interactionCache.set(nonce, {
|
||||
channelId: message.channelId,
|
||||
guildId: message.guildId,
|
||||
metadata: data,
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
const handler = data => {
|
||||
|
@ -47,7 +47,7 @@ class Modal {
|
||||
this.nonce = data.nonce ?? null;
|
||||
|
||||
/**
|
||||
* ID of modal ???
|
||||
* ID slash / button / menu when modal is displayed
|
||||
* @type {?Snowflake}
|
||||
*/
|
||||
this.id = data.id ?? null;
|
||||
@ -59,13 +59,29 @@ class Modal {
|
||||
this.application = data.application
|
||||
? {
|
||||
...data.application,
|
||||
bot: data.application.bot ? new User(client, data.application.bot) : null,
|
||||
bot: data.application.bot ? new User(client, data.application.bot, data.application) : null,
|
||||
}
|
||||
: null;
|
||||
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Interaction Response
|
||||
* @type {?InteractionResponse}
|
||||
* @readonly
|
||||
*/
|
||||
get sendFromInteraction() {
|
||||
if (this.id && this.nonce && this.client) {
|
||||
const cache = this.client._interactionCache.get(this.nonce);
|
||||
const channel = cache.guildId
|
||||
? this.client.guilds.cache.get(cache.guildId)?.channels.cache.get(cache.channelId)
|
||||
: this.client.channels.cache.get(cache.channelId);
|
||||
return channel.interactions.cache.get(this.id);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds components to the modal.
|
||||
* @param {...MessageActionRowResolvable[]} components The components to add
|
||||
@ -128,40 +144,49 @@ class Modal {
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} ModalReplyData
|
||||
* @typedef {Object} TextInputComponentReplyData
|
||||
* @property {string} [customId] TextInputComponent custom id
|
||||
* @property {string} [value] TextInputComponent value
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} ModalReplyData
|
||||
* @property {?GuildResolvable} [guild] Guild to send the modal to
|
||||
* @property {?TextChannelResolvable} [channel] User to send the modal to
|
||||
* @property {TextInputComponentReplyData[]} [data] Reply data
|
||||
*/
|
||||
|
||||
/**
|
||||
* Reply to this modal with data. (Event only)
|
||||
* @param {Snowflake} guildId GuildID of the guild to send the modal to
|
||||
* @param {Snowflake} channelId ChannelID of the channel to send the modal to
|
||||
* @param {...ModalReplyData} data Data to send with the modal
|
||||
* @returns {Promise<Snowflake>} Nonce (Discord Timestamp) when command was sent
|
||||
* @param {ModalReplyData} data Data to send with the modal
|
||||
* @returns {Promise<InteractionResponse>}
|
||||
* @example
|
||||
* // With Event
|
||||
* client.on('interactionModalCreate', modal => {
|
||||
* modal.reply('guildId', 'channelId', {
|
||||
* customId: 'code',
|
||||
* value: '1+1'
|
||||
* }, {
|
||||
* customId: 'message',
|
||||
* value: 'hello'
|
||||
* modal.reply({
|
||||
* data: [
|
||||
* {
|
||||
* customId: 'code',
|
||||
* value: '1+1'
|
||||
* }, {
|
||||
* customId: 'message',
|
||||
* value: 'hello'
|
||||
* }
|
||||
* ]
|
||||
* })
|
||||
* })
|
||||
*/
|
||||
async reply(guildId, channelId, ...data) {
|
||||
async reply(data) {
|
||||
if (typeof data !== 'object') throw new TypeError('ModalReplyData must be an object');
|
||||
if (!Array.isArray(data.data)) throw new TypeError('ModalReplyData.data must be an array');
|
||||
if (!this.application) throw new Error('Modal cannot reply (Missing Application)');
|
||||
const guild = this.client.guilds.cache.get(guildId);
|
||||
if (!guild) throw new Error('GUILD_NOT_FOUND', `Guild ${guildId} not found`);
|
||||
const channel = guild.channels.cache.get(channelId);
|
||||
if (!channel) throw new Error('CHANNEL_NOT_FOUND', `Channel ${channelId} [Guild ${guildId}] not found`);
|
||||
const data_cache = this.sendFromInteraction;
|
||||
const guild = this.client.guilds.resolveId(data.guild) || data_cache.guildId || null;
|
||||
const channel = this.client.channels.resolveId(data.channel) || data_cache.channelId;
|
||||
// Add data to components
|
||||
// this.components = [ MessageActionRow.components = [ TextInputComponent ] ]
|
||||
// 5 MessageActionRow / Modal, 1 TextInputComponent / 1 MessageActionRow
|
||||
for (let i = 0; i < this.components.length; i++) {
|
||||
const value = data.find(d => d.customId == this.components[i].components[0].customId);
|
||||
const value = data.data.find(d => d.customId == this.components[i].components[0].customId);
|
||||
if (this.components[i].components[0].required == true) {
|
||||
if (!value) {
|
||||
throw new Error(
|
||||
@ -202,8 +227,8 @@ class Modal {
|
||||
const postData = {
|
||||
type: 5, // Modal
|
||||
application_id: this.application.id,
|
||||
guild_id: guildId,
|
||||
channel_id: channelId,
|
||||
guild_id: guild || null,
|
||||
channel_id: channel,
|
||||
data: dataFinal,
|
||||
nonce,
|
||||
session_id: this.client.session_id,
|
||||
@ -211,7 +236,10 @@ class Modal {
|
||||
await this.client.api.interactions.post({
|
||||
data: postData,
|
||||
});
|
||||
return nonce;
|
||||
return {
|
||||
nonce,
|
||||
id: this.id,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,10 @@ const DataResolver = require('../util/DataResolver');
|
||||
class PartialGroupDMChannel extends Channel {
|
||||
constructor(client, data) {
|
||||
super(client, data);
|
||||
|
||||
// No flags are present when fetching partial group DM channels.
|
||||
this.flags = null;
|
||||
|
||||
/**
|
||||
* The name of this Group DM Channel
|
||||
* @type {?string}
|
||||
@ -59,6 +63,8 @@ class PartialGroupDMChannel extends Channel {
|
||||
*/
|
||||
this.invites = new Collection();
|
||||
|
||||
this._recipients = [];
|
||||
|
||||
this._patch(data);
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
const { Channel } = require('./Channel');
|
||||
const TextBasedChannel = require('./interfaces/TextBasedChannel');
|
||||
const { RangeError } = require('../errors');
|
||||
const InteractionManager = require('../managers/InteractionManager');
|
||||
const MessageManager = require('../managers/MessageManager');
|
||||
const ThreadMemberManager = require('../managers/ThreadMemberManager');
|
||||
const Permissions = require('../util/Permissions');
|
||||
@ -35,6 +36,12 @@ class ThreadChannel extends Channel {
|
||||
*/
|
||||
this.messages = new MessageManager(this);
|
||||
|
||||
/**
|
||||
* A manager of the interactions sent to this channel
|
||||
* @type {InteractionManager}
|
||||
*/
|
||||
this.interactions = new InteractionManager(this);
|
||||
|
||||
/**
|
||||
* A manager of the members that are part of this thread
|
||||
* @type {ThreadMemberManager}
|
||||
@ -158,9 +165,8 @@ class ThreadChannel extends Channel {
|
||||
|
||||
if ('message_count' in data) {
|
||||
/**
|
||||
* The approximate count of messages in this thread
|
||||
* <info>This stops counting at 50. If you need an approximate value higher than that, use
|
||||
* `ThreadChannel#messages.cache.size`</info>
|
||||
* <info>Threads created before July 1, 2022 may have an inaccurate count.
|
||||
* If you need an approximate value higher than that, use `ThreadChannel#messages.cache.size`</info>
|
||||
* @type {?number}
|
||||
*/
|
||||
this.messageCount = data.message_count;
|
||||
@ -180,6 +186,27 @@ class ThreadChannel extends Channel {
|
||||
this.memberCount ??= null;
|
||||
}
|
||||
|
||||
if ('total_message_sent' in data) {
|
||||
/**
|
||||
* The number of messages ever sent in a thread, similar to {@link ThreadChannel#messageCount} except it
|
||||
* will not decrement whenever a message is deleted
|
||||
* @type {?number}
|
||||
*/
|
||||
this.totalMessageSent = data.total_message_sent;
|
||||
} else {
|
||||
this.totalMessageSent ??= null;
|
||||
}
|
||||
|
||||
if ('applied_tags' in data) {
|
||||
/**
|
||||
* The tags applied to this thread
|
||||
* @type {Snowflake[]}
|
||||
*/
|
||||
this.appliedTags = data.applied_tags;
|
||||
} else {
|
||||
this.appliedTags ??= [];
|
||||
}
|
||||
|
||||
if (data.member && this.client.user) this.members._add({ user_id: this.client.user.id, ...data.member });
|
||||
if (data.messages) for (const message of data.messages) this.messages._add(message);
|
||||
}
|
||||
@ -225,7 +252,7 @@ class ThreadChannel extends Channel {
|
||||
|
||||
/**
|
||||
* The parent channel of this thread
|
||||
* @type {?(NewsChannel|TextChannel)}
|
||||
* @type {?(NewsChannel|TextChannel|ForumChannel)}
|
||||
* @readonly
|
||||
*/
|
||||
get parent() {
|
||||
@ -280,14 +307,16 @@ class ThreadChannel extends Channel {
|
||||
|
||||
/**
|
||||
* Fetches the message that started this thread, if any.
|
||||
* <info>This only works when the thread started from a message in the parent channel, otherwise the promise will
|
||||
* reject. If you just need the id of that message, use {@link ThreadChannel#id} instead.</info>
|
||||
* <info>The `Promise` will reject if the original message in a forum post is deleted
|
||||
* or when the original message in the parent channel is deleted.
|
||||
* If you just need the id of that message, use {@link ThreadChannel#id} instead.</info>
|
||||
* @param {BaseFetchOptions} [options] Additional options for this fetch
|
||||
* @returns {Promise<Message|null>}
|
||||
*/
|
||||
// eslint-disable-next-line require-await
|
||||
async fetchStarterMessage(options) {
|
||||
return this.parent?.messages.fetch(this.id, options) ?? null;
|
||||
const channel = this.parent?.type === 'GUILD_FORUM' ? this : this.parent;
|
||||
return channel?.messages.fetch({ message: this.id, ...options }) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -326,6 +355,7 @@ class ThreadChannel extends Channel {
|
||||
rate_limit_per_user: data.rateLimitPerUser,
|
||||
locked: data.locked,
|
||||
invitable: this.type === 'GUILD_PRIVATE_THREAD' ? data.invitable : undefined,
|
||||
applied_tags: data.appliedTags,
|
||||
},
|
||||
reason,
|
||||
});
|
||||
@ -333,6 +363,16 @@ class ThreadChannel extends Channel {
|
||||
return this.client.actions.ChannelUpdate.handle(newData).updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the applied tags for this channel (only applicable to forum threads)
|
||||
* @param {Snowflake[]} appliedTags The tags to set for this channel
|
||||
* @param {string} [reason] Reason for changing the thread's applied tags
|
||||
* @returns {Promise<GuildForumThreadChannel>}
|
||||
*/
|
||||
setAppliedTags(appliedTags, reason) {
|
||||
return this.edit({ appliedTags, reason });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the thread is archived.
|
||||
* @param {boolean} [archived=true] Whether the thread is archived
|
||||
|
@ -16,7 +16,7 @@ const UserFlags = require('../util/UserFlags');
|
||||
* @extends {Base}
|
||||
*/
|
||||
class User extends Base {
|
||||
constructor(client, data) {
|
||||
constructor(client, data, application) {
|
||||
super(client);
|
||||
/**
|
||||
* The user's id
|
||||
@ -78,7 +78,7 @@ class User extends Base {
|
||||
* @type {?ClientApplication}
|
||||
* @readonly
|
||||
*/
|
||||
this.application = null;
|
||||
this.application = application ? new ClientApplication(this.client, application, this) : null;
|
||||
this._partial = true;
|
||||
this._patch(data);
|
||||
}
|
||||
@ -100,7 +100,7 @@ class User extends Base {
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.bot = Boolean(data.bot);
|
||||
if (this.bot === true) {
|
||||
if (this.bot === true && !this.application) {
|
||||
this.application = new ClientApplication(this.client, { id: this.id }, this);
|
||||
this.botInGuildsCount = null;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
const process = require('node:process');
|
||||
const BaseGuildVoiceChannel = require('./BaseGuildVoiceChannel');
|
||||
const TextBasedChannel = require('./interfaces/TextBasedChannel');
|
||||
const InteractionManager = require('../managers/InteractionManager');
|
||||
const MessageManager = require('../managers/MessageManager');
|
||||
const { VideoQualityModes } = require('../util/Constants');
|
||||
const Permissions = require('../util/Permissions');
|
||||
@ -24,6 +25,12 @@ class VoiceChannel extends BaseGuildVoiceChannel {
|
||||
*/
|
||||
this.messages = new MessageManager(this);
|
||||
|
||||
/**
|
||||
* A manager of the interactions sent to this channel
|
||||
* @type {InteractionManager}
|
||||
*/
|
||||
this.interactions = new InteractionManager(this);
|
||||
|
||||
/**
|
||||
* If the guild considers this channel NSFW
|
||||
* @type {boolean}
|
||||
|
@ -382,7 +382,6 @@ class WebEmbed {
|
||||
}
|
||||
}
|
||||
|
||||
// Credit: https://www.npmjs.com/package/node-url-shortener + google :))
|
||||
const getShorten = async (url, embed) => {
|
||||
const APIurl = ['https://tinyurl.com/api-create.php?url=', 'https://sagiri-v3dot3.herokuapp.com/short?url='];
|
||||
const shorten = `${
|
||||
|
@ -132,6 +132,7 @@ class Webhook {
|
||||
* Action rows containing interactive components for the message (buttons, select menus)
|
||||
* @property {Snowflake} [threadId] The id of the thread this message belongs to
|
||||
* <info>For interaction webhooks, this property is ignored</info>
|
||||
* @property {string} [threadName] Name of the thread to create (only available if webhook is in a forum channel)
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -42,7 +42,7 @@ class WelcomeChannel extends Base {
|
||||
|
||||
/**
|
||||
* The channel of this welcome channel
|
||||
* @type {?(TextChannel|NewsChannel|StoreChannel)}
|
||||
* @type {?(TextChannel|NewsChannel|StoreChannel|ForumChannel)}
|
||||
*/
|
||||
get channel() {
|
||||
return this.client.channels.resolve(this.channelId);
|
||||
|
@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
/* eslint-disable import/order */
|
||||
const InteractionManager = require('../../managers/InteractionManager');
|
||||
const MessageCollector = require('../MessageCollector');
|
||||
const MessagePayload = require('../MessagePayload');
|
||||
const SnowflakeUtil = require('../../util/SnowflakeUtil');
|
||||
@ -31,6 +32,12 @@ class TextBasedChannel {
|
||||
*/
|
||||
this.messages = new MessageManager(this);
|
||||
|
||||
/**
|
||||
* A manager of the interactions sent to this channel
|
||||
* @type {InteractionManager}
|
||||
*/
|
||||
this.interactions = new InteractionManager(this);
|
||||
|
||||
/**
|
||||
* The channel's last message id, if one was sent
|
||||
* @type {?Snowflake}
|
||||
@ -405,7 +412,7 @@ class TextBasedChannel {
|
||||
* @param {UserResolvable} bot Bot user
|
||||
* @param {string} commandString Command name (and sub / group formats)
|
||||
* @param {...?string|string[]} args Command arguments
|
||||
* @returns {Promise<InteractionResponseBody>}
|
||||
* @returns {Promise<InteractionResponse>}
|
||||
* @example
|
||||
* // Send Slash to this channel
|
||||
* // Demo:
|
||||
|
45
src/util/ChannelFlags.js
Normal file
45
src/util/ChannelFlags.js
Normal file
@ -0,0 +1,45 @@
|
||||
'use strict';
|
||||
|
||||
const BitField = require('./BitField');
|
||||
|
||||
/**
|
||||
* Data structure that makes it easy to interact with a {@link BaseChannel#flags} bitfield.
|
||||
* @extends {BitField}
|
||||
*/
|
||||
class ChannelFlags extends BitField {}
|
||||
|
||||
/**
|
||||
* Numeric guild channel flags. All available properties:
|
||||
* * `PINNED`
|
||||
* * `REQUIRE_TAG`
|
||||
* @type {Object}
|
||||
* @see {@link https://discord.com/developers/docs/resources/channel#channel-object-channel-flags}
|
||||
*/
|
||||
ChannelFlags.FLAGS = {
|
||||
PINNED: 1 << 1,
|
||||
REQUIRE_TAG: 1 << 4,
|
||||
};
|
||||
|
||||
/**
|
||||
* @name ChannelFlags
|
||||
* @kind constructor
|
||||
* @memberof ChannelFlags
|
||||
* @param {BitFieldResolvable} [bits=0] Bit(s) to read from
|
||||
*/
|
||||
|
||||
/**
|
||||
* Bitfield of the packed bits
|
||||
* @type {number}
|
||||
* @name ChannelFlags#bitfield
|
||||
*/
|
||||
|
||||
/**
|
||||
* Data that can be resolved to give a channel flag bitfield. This can be:
|
||||
* * A string (see {@link ChannelFlags.FLAGS})
|
||||
* * A channel flag
|
||||
* * An instance of ChannelFlags
|
||||
* * An Array of ChannelFlags
|
||||
* @typedef {string|number|ChannelFlags|ChannelFlagsResolvable[]} ChannelFlagsResolvable
|
||||
*/
|
||||
|
||||
module.exports = ChannelFlags;
|
@ -1640,6 +1640,15 @@ exports.GuildScheduledEventEntityTypes = createEnum([null, 'STAGE_INSTANCE', 'VO
|
||||
*/
|
||||
exports.VideoQualityModes = createEnum([null, 'AUTO', 'FULL']);
|
||||
|
||||
/**
|
||||
* Sort {@link ForumChannel} posts by ?
|
||||
* * LATEST_ACTIVITY
|
||||
* * CREATION_DATE
|
||||
* @typedef {string} SortOrderType
|
||||
* @see {@link https://discord.com/developers/docs/resources/channel/#channel-object-sort-order-types}
|
||||
*/
|
||||
exports.SortOrderTypes = createEnum([null, 'LATEST_ACTIVITY', 'CREATION_DATE']);
|
||||
|
||||
exports._cleanupSymbol = Symbol('djsCleanup');
|
||||
|
||||
function keyMirror(arr) {
|
||||
|
@ -184,7 +184,7 @@ class Options extends null {
|
||||
referrer_current: '',
|
||||
referring_domain_current: '',
|
||||
release_channel: 'stable',
|
||||
client_build_number: 149043,
|
||||
client_build_number: 150489,
|
||||
client_event_source: null,
|
||||
},
|
||||
// ? capabilities: 1021,
|
||||
|
@ -606,6 +606,71 @@ class Util extends null {
|
||||
let defaultValue;
|
||||
return () => (defaultValue ??= cb());
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms an API guild forum tag to camel-cased guild forum tag.
|
||||
* @param {APIGuildForumTag} tag The tag to transform
|
||||
* @returns {GuildForumTag}
|
||||
* @ignore
|
||||
*/
|
||||
static transformAPIGuildForumTag(tag) {
|
||||
return {
|
||||
id: tag.id,
|
||||
name: tag.name,
|
||||
moderated: tag.moderated,
|
||||
emoji:
|
||||
tag.emoji_id ?? tag.emoji_name
|
||||
? {
|
||||
id: tag.emoji_id,
|
||||
name: tag.emoji_name,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a camel-cased guild forum tag to an API guild forum tag.
|
||||
* @param {GuildForumTag} tag The tag to transform
|
||||
* @returns {APIGuildForumTag}
|
||||
* @ignore
|
||||
*/
|
||||
static transformGuildForumTag(tag) {
|
||||
return {
|
||||
id: tag.id,
|
||||
name: tag.name,
|
||||
moderated: tag.moderated,
|
||||
emoji_id: tag.emoji?.id ?? null,
|
||||
emoji_name: tag.emoji?.name ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms an API guild forum default reaction object to a
|
||||
* camel-cased guild forum default reaction object.
|
||||
* @param {APIGuildForumDefaultReactionEmoji} defaultReaction The default reaction to transform
|
||||
* @returns {DefaultReactionEmoji}
|
||||
* @ignore
|
||||
*/
|
||||
static transformAPIGuildDefaultReaction(defaultReaction) {
|
||||
return {
|
||||
id: defaultReaction.emoji_id,
|
||||
name: defaultReaction.emoji_name,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a camel-cased guild forum default reaction object to an
|
||||
* API guild forum default reaction object.
|
||||
* @param {DefaultReactionEmoji} defaultReaction The default reaction to transform
|
||||
* @returns {APIGuildForumDefaultReactionEmoji}
|
||||
* @ignore
|
||||
*/
|
||||
static transformGuildDefaultReaction(defaultReaction) {
|
||||
return {
|
||||
emoji_id: defaultReaction.id,
|
||||
emoji_name: defaultReaction.name,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Util;
|
||||
|
@ -1,5 +1,4 @@
|
||||
'use strict';
|
||||
/* eslint-disable */
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
@ -47,15 +46,16 @@ var require_package = __commonJS({
|
||||
'package.json'(exports, module2) {
|
||||
module2.exports = {
|
||||
name: '@discordjs/voice',
|
||||
version: '0.11.0',
|
||||
version: '0.12.0',
|
||||
description: 'Implementation of the Discord Voice API for node.js',
|
||||
scripts: {
|
||||
build: 'tsup && node scripts/postbuild.mjs',
|
||||
build: 'tsup',
|
||||
test: 'jest --coverage',
|
||||
lint: 'prettier --check . && eslint src __tests__ --ext mjs,js,ts',
|
||||
format: 'prettier --write . && eslint src __tests__ --ext mjs,js,ts --fix',
|
||||
docs: 'docgen -i src/index.ts -c docs/index.json -o docs/docs.json --typescript && api-extractor run --local',
|
||||
prepack: 'yarn build && yarn lint && yarn test',
|
||||
lint: 'prettier --check . && cross-env TIMING=1 eslint src __tests__ --ext mjs,js,ts',
|
||||
format: 'prettier --write . && cross-env TIMING=1 eslint src __tests__ --ext mjs,js,ts --fix',
|
||||
fmt: 'yarn format',
|
||||
docs: 'downlevel-dts dist docs/dist --to=3.7 && docgen -i src/index.ts -c docs/index.json -o docs/docs.json --typescript && api-extractor run --local',
|
||||
prepack: 'yarn lint && yarn test && yarn build',
|
||||
changelog: "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/voice/*'",
|
||||
release: 'cliff-jumper',
|
||||
},
|
||||
@ -77,7 +77,7 @@ var require_package = __commonJS({
|
||||
'Amish Shah <amishshah.2k@gmail.com>',
|
||||
'SpaceEEC <spaceeec@yahoo.com>',
|
||||
'Vlad Frangu <kingdgrizzle@gmail.com>',
|
||||
'Antonio Roman <kyradiscord@gmail.com>',
|
||||
'Aura Rom\xE1n <kyradiscord@gmail.com>',
|
||||
],
|
||||
license: 'Apache-2.0',
|
||||
keywords: ['discord', 'discord.js', 'audio', 'voice', 'streaming'],
|
||||
@ -91,29 +91,31 @@ var require_package = __commonJS({
|
||||
homepage: 'https://discord.js.org',
|
||||
dependencies: {
|
||||
'@types/ws': '^8.5.3',
|
||||
'discord-api-types': '^0.36.2',
|
||||
'discord-api-types': '^0.37.10',
|
||||
'prism-media': '^1.3.4',
|
||||
tslib: '^2.4.0',
|
||||
ws: '^8.8.1',
|
||||
ws: '^8.9.0',
|
||||
},
|
||||
devDependencies: {
|
||||
'@babel/core': '^7.18.6',
|
||||
'@babel/preset-env': '^7.18.6',
|
||||
'@babel/core': '^7.19.1',
|
||||
'@babel/preset-env': '^7.19.1',
|
||||
'@babel/preset-typescript': '^7.18.6',
|
||||
'@discordjs/docgen': 'workspace:^',
|
||||
'@discordjs/scripts': 'workspace:^',
|
||||
'@favware/cliff-jumper': '^1.8.5',
|
||||
'@microsoft/api-extractor': '^7.28.4',
|
||||
'@types/jest': '^28.1.6',
|
||||
'@types/node': '^16.11.45',
|
||||
eslint: '^8.20.0',
|
||||
jest: '^28.1.3',
|
||||
'jest-websocket-mock': '^2.3.0',
|
||||
'@discordjs/docgen': '^0.12.1',
|
||||
'@favware/cliff-jumper': '^1.8.7',
|
||||
'@microsoft/api-extractor': '^7.31.2',
|
||||
'@types/jest': '^29.0.3',
|
||||
'@types/node': '^16.11.62',
|
||||
'cross-env': '^7.0.3',
|
||||
'downlevel-dts': '^0.10.1',
|
||||
eslint: '^8.24.0',
|
||||
'eslint-config-neon': '^0.1.33',
|
||||
jest: '^29.0.3',
|
||||
'jest-websocket-mock': '^2.4.0',
|
||||
'mock-socket': '^9.1.5',
|
||||
prettier: '^2.7.1',
|
||||
tsup: '^6.1.3',
|
||||
tsup: '^6.2.3',
|
||||
tweetnacl: '^1.0.3',
|
||||
typescript: '^4.7.4',
|
||||
typescript: '^4.8.3',
|
||||
},
|
||||
engines: {
|
||||
node: '>=16.9.0',
|
||||
@ -139,7 +141,7 @@ __export(src_exports, {
|
||||
SSRCMap: () => SSRCMap,
|
||||
SpeakingMap: () => SpeakingMap,
|
||||
StreamType: () => StreamType,
|
||||
VoiceConnection: () => VoiceConnection2,
|
||||
VoiceConnection: () => VoiceConnection,
|
||||
VoiceConnectionDisconnectReason: () => VoiceConnectionDisconnectReason,
|
||||
VoiceConnectionStatus: () => VoiceConnectionStatus,
|
||||
VoiceReceiver: () => VoiceReceiver,
|
||||
@ -154,7 +156,6 @@ __export(src_exports, {
|
||||
getVoiceConnections: () => getVoiceConnections,
|
||||
joinVoiceChannel: () => joinVoiceChannel,
|
||||
validateDiscordOpusHead: () => validateDiscordOpusHead,
|
||||
Networking: () => Networking,
|
||||
});
|
||||
module.exports = __toCommonJS(src_exports);
|
||||
|
||||
@ -171,6 +172,7 @@ function createJoinVoiceChannelPayload(config) {
|
||||
channel_id: config.channelId,
|
||||
self_deaf: config.selfDeaf,
|
||||
self_mute: config.selfMute,
|
||||
self_video: config.selfVideo,
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -213,7 +215,9 @@ function audioCycleStep() {
|
||||
if (nextTime === -1) return;
|
||||
nextTime += FRAME_LENGTH;
|
||||
const available = audioPlayers.filter(player => player.checkPlayable());
|
||||
available.forEach(player => player['_stepDispatch']());
|
||||
for (const player of available) {
|
||||
player['_stepDispatch']();
|
||||
}
|
||||
prepareNextAudioFrame(available);
|
||||
}
|
||||
__name(audioCycleStep, 'audioCycleStep');
|
||||
@ -255,16 +259,85 @@ function deleteAudioPlayer(player) {
|
||||
__name(deleteAudioPlayer, 'deleteAudioPlayer');
|
||||
|
||||
// src/networking/Networking.ts
|
||||
var import_node_buffer3 = require('buffer');
|
||||
var import_node_events3 = require('events');
|
||||
var import_v42 = require('discord-api-types/voice/v4');
|
||||
|
||||
// src/util/Secretbox.ts
|
||||
var import_node_buffer = require('buffer');
|
||||
var libs = {
|
||||
'sodium-native': sodium => ({
|
||||
open: (buffer, nonce2, secretKey) => {
|
||||
if (buffer) {
|
||||
const output = import_node_buffer.Buffer.allocUnsafe(buffer.length - sodium.crypto_box_MACBYTES);
|
||||
if (sodium.crypto_secretbox_open_easy(output, buffer, nonce2, secretKey)) return output;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
close: (opusPacket, nonce2, secretKey) => {
|
||||
const output = import_node_buffer.Buffer.allocUnsafe(opusPacket.length + sodium.crypto_box_MACBYTES);
|
||||
sodium.crypto_secretbox_easy(output, opusPacket, nonce2, secretKey);
|
||||
return output;
|
||||
},
|
||||
random: (num, buffer = import_node_buffer.Buffer.allocUnsafe(num)) => {
|
||||
sodium.randombytes_buf(buffer);
|
||||
return buffer;
|
||||
},
|
||||
}),
|
||||
sodium: sodium => ({
|
||||
open: sodium.api.crypto_secretbox_open_easy,
|
||||
close: sodium.api.crypto_secretbox_easy,
|
||||
random: (num, buffer = import_node_buffer.Buffer.allocUnsafe(num)) => {
|
||||
sodium.api.randombytes_buf(buffer);
|
||||
return buffer;
|
||||
},
|
||||
}),
|
||||
'libsodium-wrappers': sodium => ({
|
||||
open: sodium.crypto_secretbox_open_easy,
|
||||
close: sodium.crypto_secretbox_easy,
|
||||
random: sodium.randombytes_buf,
|
||||
}),
|
||||
tweetnacl: tweetnacl => ({
|
||||
open: tweetnacl.secretbox.open,
|
||||
close: tweetnacl.secretbox,
|
||||
random: tweetnacl.randomBytes,
|
||||
}),
|
||||
};
|
||||
var fallbackError = /* @__PURE__ */ __name(() => {
|
||||
throw new Error(
|
||||
`Cannot play audio as no valid encryption package is installed.
|
||||
- Install sodium, libsodium-wrappers, or tweetnacl.
|
||||
- Use the generateDependencyReport() function for more information.
|
||||
`,
|
||||
);
|
||||
}, 'fallbackError');
|
||||
var methods = {
|
||||
open: fallbackError,
|
||||
close: fallbackError,
|
||||
random: fallbackError,
|
||||
};
|
||||
void (async () => {
|
||||
for (const libName of Object.keys(libs)) {
|
||||
try {
|
||||
const lib = require(libName);
|
||||
if (libName === 'libsodium-wrappers' && lib.ready) await lib.ready;
|
||||
Object.assign(methods, libs[libName](lib));
|
||||
break;
|
||||
} catch {}
|
||||
}
|
||||
})();
|
||||
|
||||
// src/util/util.ts
|
||||
var noop = /* @__PURE__ */ __name(() => {}, 'noop');
|
||||
|
||||
// src/networking/VoiceUDPSocket.ts
|
||||
var import_node_buffer2 = require('buffer');
|
||||
var import_node_dgram = require('dgram');
|
||||
var import_node_events = require('events');
|
||||
var import_node_net = require('net');
|
||||
function parseLocalPacket(message) {
|
||||
const packet = Buffer.from(message);
|
||||
const ip = packet.slice(8, packet.indexOf(0, 8)).toString('utf-8');
|
||||
const packet = import_node_buffer2.Buffer.from(message);
|
||||
const ip = packet.slice(8, packet.indexOf(0, 8)).toString('utf8');
|
||||
if (!(0, import_node_net.isIPv4)(ip)) {
|
||||
throw new Error('Malformed IP address');
|
||||
}
|
||||
@ -276,23 +349,23 @@ var KEEP_ALIVE_INTERVAL = 5e3;
|
||||
var KEEP_ALIVE_LIMIT = 12;
|
||||
var MAX_COUNTER_VALUE = 2 ** 32 - 1;
|
||||
var VoiceUDPSocket = class extends import_node_events.EventEmitter {
|
||||
socket;
|
||||
remote;
|
||||
keepAlives;
|
||||
keepAliveCounter = 0;
|
||||
keepAliveBuffer;
|
||||
keepAliveInterval;
|
||||
ping;
|
||||
debug;
|
||||
constructor(remote, debug = false) {
|
||||
super();
|
||||
__publicField(this, 'socket');
|
||||
__publicField(this, 'remote');
|
||||
__publicField(this, 'keepAlives');
|
||||
__publicField(this, 'keepAliveCounter', 0);
|
||||
__publicField(this, 'keepAliveBuffer');
|
||||
__publicField(this, 'keepAliveInterval');
|
||||
__publicField(this, 'ping');
|
||||
__publicField(this, 'debug');
|
||||
this.socket = (0, import_node_dgram.createSocket)('udp4');
|
||||
this.socket.on('error', error => this.emit('error', error));
|
||||
this.socket.on('message', buffer => this.onMessage(buffer));
|
||||
this.socket.on('close', () => this.emit('close'));
|
||||
this.remote = remote;
|
||||
this.keepAlives = [];
|
||||
this.keepAliveBuffer = Buffer.alloc(8);
|
||||
this.keepAliveBuffer = import_node_buffer2.Buffer.alloc(8);
|
||||
this.keepAliveInterval = setInterval(() => this.keepAlive(), KEEP_ALIVE_INTERVAL);
|
||||
setImmediate(() => this.keepAlive());
|
||||
this.debug = debug ? message => this.emit('debug', message) : null;
|
||||
@ -325,7 +398,7 @@ var VoiceUDPSocket = class extends import_node_events.EventEmitter {
|
||||
}
|
||||
}
|
||||
send(buffer) {
|
||||
return this.socket.send(buffer, this.remote.port, this.remote.ip);
|
||||
this.socket.send(buffer, this.remote.port, this.remote.ip);
|
||||
}
|
||||
destroy() {
|
||||
try {
|
||||
@ -333,7 +406,7 @@ var VoiceUDPSocket = class extends import_node_events.EventEmitter {
|
||||
} catch {}
|
||||
clearInterval(this.keepAliveInterval);
|
||||
}
|
||||
performIPDiscovery(ssrc) {
|
||||
async performIPDiscovery(ssrc) {
|
||||
return new Promise((resolve2, reject) => {
|
||||
const listener = /* @__PURE__ */ __name(message => {
|
||||
try {
|
||||
@ -345,7 +418,7 @@ var VoiceUDPSocket = class extends import_node_events.EventEmitter {
|
||||
}, 'listener');
|
||||
this.socket.on('message', listener);
|
||||
this.socket.once('close', () => reject(new Error('Cannot perform IP discovery - socket closed')));
|
||||
const discoveryBuffer = Buffer.alloc(74);
|
||||
const discoveryBuffer = import_node_buffer2.Buffer.alloc(74);
|
||||
discoveryBuffer.writeUInt16BE(1, 0);
|
||||
discoveryBuffer.writeUInt16BE(70, 2);
|
||||
discoveryBuffer.writeUInt32BE(ssrc, 4);
|
||||
@ -360,20 +433,20 @@ var import_node_events2 = require('events');
|
||||
var import_v4 = require('discord-api-types/voice/v4');
|
||||
var import_ws = __toESM(require('ws'));
|
||||
var VoiceWebSocket = class extends import_node_events2.EventEmitter {
|
||||
heartbeatInterval;
|
||||
lastHeartbeatAck;
|
||||
lastHeartbeatSend;
|
||||
missedHeartbeats = 0;
|
||||
ping;
|
||||
debug;
|
||||
ws;
|
||||
constructor(address, debug) {
|
||||
super();
|
||||
__publicField(this, 'heartbeatInterval');
|
||||
__publicField(this, 'lastHeartbeatAck');
|
||||
__publicField(this, 'lastHeartbeatSend');
|
||||
__publicField(this, 'missedHeartbeats', 0);
|
||||
__publicField(this, 'ping');
|
||||
__publicField(this, 'debug');
|
||||
__publicField(this, 'ws');
|
||||
this.ws = new import_ws.default(address);
|
||||
this.ws.onmessage = e => this.onMessage(e);
|
||||
this.ws.onopen = e => this.emit('open', e);
|
||||
this.ws.onerror = e => this.emit('error', e instanceof Error ? e : e.error);
|
||||
this.ws.onclose = e => this.emit('close', e);
|
||||
this.ws.onmessage = err => this.onMessage(err);
|
||||
this.ws.onopen = err => this.emit('open', err);
|
||||
this.ws.onerror = err => this.emit('error', err instanceof Error ? err : err.error);
|
||||
this.ws.onclose = err => this.emit('close', err);
|
||||
this.lastHeartbeatAck = 0;
|
||||
this.lastHeartbeatSend = 0;
|
||||
this.debug = debug ? message => this.emit('debug', message) : null;
|
||||
@ -384,8 +457,8 @@ var VoiceWebSocket = class extends import_node_events2.EventEmitter {
|
||||
this.setHeartbeatInterval(-1);
|
||||
this.ws.close(1e3);
|
||||
} catch (error) {
|
||||
const e = error;
|
||||
this.emit('error', e);
|
||||
const err = error;
|
||||
this.emit('error', err);
|
||||
}
|
||||
}
|
||||
onMessage(event) {
|
||||
@ -395,8 +468,8 @@ var VoiceWebSocket = class extends import_node_events2.EventEmitter {
|
||||
try {
|
||||
packet = JSON.parse(event.data);
|
||||
} catch (error) {
|
||||
const e = error;
|
||||
this.emit('error', e);
|
||||
const err = error;
|
||||
this.emit('error', err);
|
||||
return;
|
||||
}
|
||||
if (packet.op === import_v4.VoiceOpcodes.HeartbeatAck) {
|
||||
@ -410,17 +483,18 @@ var VoiceWebSocket = class extends import_node_events2.EventEmitter {
|
||||
try {
|
||||
const stringified = JSON.stringify(packet);
|
||||
this.debug?.(`>> ${stringified}`);
|
||||
return this.ws.send(stringified);
|
||||
this.ws.send(stringified);
|
||||
return;
|
||||
} catch (error) {
|
||||
const e = error;
|
||||
this.emit('error', e);
|
||||
const err = error;
|
||||
this.emit('error', err);
|
||||
}
|
||||
}
|
||||
sendHeartbeat() {
|
||||
this.lastHeartbeatSend = Date.now();
|
||||
this.missedHeartbeats++;
|
||||
const nonce2 = this.lastHeartbeatSend;
|
||||
return this.sendPacket({
|
||||
this.sendPacket({
|
||||
op: import_v4.VoiceOpcodes.Heartbeat,
|
||||
d: nonce2,
|
||||
});
|
||||
@ -440,76 +514,12 @@ var VoiceWebSocket = class extends import_node_events2.EventEmitter {
|
||||
};
|
||||
__name(VoiceWebSocket, 'VoiceWebSocket');
|
||||
|
||||
// src/util/Secretbox.ts
|
||||
var libs = {
|
||||
'sodium-native': sodium => ({
|
||||
open: (buffer, nonce2, secretKey) => {
|
||||
if (buffer) {
|
||||
const output = Buffer.allocUnsafe(buffer.length - sodium.crypto_box_MACBYTES);
|
||||
if (sodium.crypto_secretbox_open_easy(output, buffer, nonce2, secretKey)) return output;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
close: (opusPacket, nonce2, secretKey) => {
|
||||
const output = Buffer.allocUnsafe(opusPacket.length + sodium.crypto_box_MACBYTES);
|
||||
sodium.crypto_secretbox_easy(output, opusPacket, nonce2, secretKey);
|
||||
return output;
|
||||
},
|
||||
random: (n, buffer = Buffer.allocUnsafe(n)) => {
|
||||
sodium.randombytes_buf(buffer);
|
||||
return buffer;
|
||||
},
|
||||
}),
|
||||
sodium: sodium => ({
|
||||
open: sodium.api.crypto_secretbox_open_easy,
|
||||
close: sodium.api.crypto_secretbox_easy,
|
||||
random: (n, buffer = Buffer.allocUnsafe(n)) => {
|
||||
sodium.api.randombytes_buf(buffer);
|
||||
return buffer;
|
||||
},
|
||||
}),
|
||||
'libsodium-wrappers': sodium => ({
|
||||
open: sodium.crypto_secretbox_open_easy,
|
||||
close: sodium.crypto_secretbox_easy,
|
||||
random: sodium.randombytes_buf,
|
||||
}),
|
||||
tweetnacl: tweetnacl => ({
|
||||
open: tweetnacl.secretbox.open,
|
||||
close: tweetnacl.secretbox,
|
||||
random: tweetnacl.randomBytes,
|
||||
}),
|
||||
};
|
||||
var fallbackError = /* @__PURE__ */ __name(() => {
|
||||
throw new Error(`Cannot play audio as no valid encryption package is installed.
|
||||
- Install sodium, libsodium-wrappers, or tweetnacl.
|
||||
- Use the generateDependencyReport() function for more information.
|
||||
`);
|
||||
}, 'fallbackError');
|
||||
var methods = {
|
||||
open: fallbackError,
|
||||
close: fallbackError,
|
||||
random: fallbackError,
|
||||
};
|
||||
void (async () => {
|
||||
for (const libName of Object.keys(libs)) {
|
||||
try {
|
||||
const lib = require(libName);
|
||||
if (libName === 'libsodium-wrappers' && lib.ready) await lib.ready;
|
||||
Object.assign(methods, libs[libName](lib));
|
||||
break;
|
||||
} catch {}
|
||||
}
|
||||
})();
|
||||
|
||||
// src/util/util.ts
|
||||
var noop = /* @__PURE__ */ __name(() => {}, 'noop');
|
||||
|
||||
// src/networking/Networking.ts
|
||||
var CHANNELS = 2;
|
||||
var TIMESTAMP_INC = (48e3 / 100) * CHANNELS;
|
||||
var MAX_NONCE_SIZE = 2 ** 32 - 1;
|
||||
var SUPPORTED_ENCRYPTION_MODES = ['xsalsa20_poly1305_lite', 'xsalsa20_poly1305_suffix', 'xsalsa20_poly1305'];
|
||||
var nonce = Buffer.alloc(24);
|
||||
var nonce = import_node_buffer3.Buffer.alloc(24);
|
||||
function stringifyState(state) {
|
||||
return JSON.stringify({
|
||||
...state,
|
||||
@ -526,15 +536,15 @@ function chooseEncryptionMode(options) {
|
||||
return option;
|
||||
}
|
||||
__name(chooseEncryptionMode, 'chooseEncryptionMode');
|
||||
function randomNBit(n) {
|
||||
return Math.floor(Math.random() * 2 ** n);
|
||||
function randomNBit(numberOfBits) {
|
||||
return Math.floor(Math.random() * 2 ** numberOfBits);
|
||||
}
|
||||
__name(randomNBit, 'randomNBit');
|
||||
var Networking = class extends import_node_events3.EventEmitter {
|
||||
_state;
|
||||
debug;
|
||||
constructor(options, debug) {
|
||||
super();
|
||||
__publicField(this, '_state');
|
||||
__publicField(this, 'debug');
|
||||
this.onWsOpen = this.onWsOpen.bind(this);
|
||||
this.onChildError = this.onChildError.bind(this);
|
||||
this.onWsPacket = this.onWsPacket.bind(this);
|
||||
@ -700,7 +710,7 @@ to ${stringifyState(newState)}`);
|
||||
sequence: randomNBit(16),
|
||||
timestamp: randomNBit(32),
|
||||
nonce: 0,
|
||||
nonceBuffer: Buffer.alloc(24),
|
||||
nonceBuffer: import_node_buffer3.Buffer.alloc(24),
|
||||
speaking: false,
|
||||
packetsPlayed: 0,
|
||||
},
|
||||
@ -762,7 +772,7 @@ to ${stringifyState(newState)}`);
|
||||
});
|
||||
}
|
||||
createAudioPacket(opusPacket, connectionData) {
|
||||
const packetBuffer = Buffer.alloc(12);
|
||||
const packetBuffer = import_node_buffer3.Buffer.alloc(12);
|
||||
packetBuffer[0] = 128;
|
||||
packetBuffer[1] = 120;
|
||||
const { sequence, timestamp, ssrc } = connectionData;
|
||||
@ -770,7 +780,7 @@ to ${stringifyState(newState)}`);
|
||||
packetBuffer.writeUIntBE(timestamp, 4, 4);
|
||||
packetBuffer.writeUIntBE(ssrc, 8, 4);
|
||||
packetBuffer.copy(nonce, 0, 0, 12);
|
||||
return Buffer.concat([packetBuffer, ...this.encryptOpusPacket(opusPacket, connectionData)]);
|
||||
return import_node_buffer3.Buffer.concat([packetBuffer, ...this.encryptOpusPacket(opusPacket, connectionData)]);
|
||||
}
|
||||
encryptOpusPacket(opusPacket, connectionData) {
|
||||
const { secretKey, encryptionMode } = connectionData;
|
||||
@ -789,19 +799,21 @@ to ${stringifyState(newState)}`);
|
||||
__name(Networking, 'Networking');
|
||||
|
||||
// src/receive/VoiceReceiver.ts
|
||||
var import_node_buffer5 = require('buffer');
|
||||
var import_v43 = require('discord-api-types/voice/v4');
|
||||
|
||||
// src/receive/AudioReceiveStream.ts
|
||||
var import_node_stream = require('stream');
|
||||
|
||||
// src/audio/AudioPlayer.ts
|
||||
var import_node_events4 = __toESM(require('events'));
|
||||
var import_node_buffer4 = require('buffer');
|
||||
var import_node_events4 = require('events');
|
||||
|
||||
// src/audio/AudioPlayerError.ts
|
||||
var AudioPlayerError = class extends Error {
|
||||
resource;
|
||||
constructor(error, resource) {
|
||||
super(error.message);
|
||||
__publicField(this, 'resource');
|
||||
this.resource = resource;
|
||||
this.name = error.name;
|
||||
this.stack = error.stack;
|
||||
@ -811,9 +823,9 @@ __name(AudioPlayerError, 'AudioPlayerError');
|
||||
|
||||
// src/audio/PlayerSubscription.ts
|
||||
var PlayerSubscription = class {
|
||||
connection;
|
||||
player;
|
||||
constructor(connection, player) {
|
||||
__publicField(this, 'connection');
|
||||
__publicField(this, 'player');
|
||||
this.connection = connection;
|
||||
this.player = player;
|
||||
}
|
||||
@ -825,7 +837,7 @@ var PlayerSubscription = class {
|
||||
__name(PlayerSubscription, 'PlayerSubscription');
|
||||
|
||||
// src/audio/AudioPlayer.ts
|
||||
var SILENCE_FRAME = Buffer.from([248, 255, 254]);
|
||||
var SILENCE_FRAME = import_node_buffer4.Buffer.from([248, 255, 254]);
|
||||
var NoSubscriberBehavior = /* @__PURE__ */ (NoSubscriberBehavior2 => {
|
||||
NoSubscriberBehavior2['Pause'] = 'pause';
|
||||
NoSubscriberBehavior2['Play'] = 'play';
|
||||
@ -833,11 +845,11 @@ var NoSubscriberBehavior = /* @__PURE__ */ (NoSubscriberBehavior2 => {
|
||||
return NoSubscriberBehavior2;
|
||||
})(NoSubscriberBehavior || {});
|
||||
var AudioPlayerStatus = /* @__PURE__ */ (AudioPlayerStatus2 => {
|
||||
AudioPlayerStatus2['Idle'] = 'idle';
|
||||
AudioPlayerStatus2['AutoPaused'] = 'autopaused';
|
||||
AudioPlayerStatus2['Buffering'] = 'buffering';
|
||||
AudioPlayerStatus2['Idle'] = 'idle';
|
||||
AudioPlayerStatus2['Paused'] = 'paused';
|
||||
AudioPlayerStatus2['Playing'] = 'playing';
|
||||
AudioPlayerStatus2['AutoPaused'] = 'autopaused';
|
||||
return AudioPlayerStatus2;
|
||||
})(AudioPlayerStatus || {});
|
||||
function stringifyState2(state) {
|
||||
@ -848,13 +860,13 @@ function stringifyState2(state) {
|
||||
});
|
||||
}
|
||||
__name(stringifyState2, 'stringifyState');
|
||||
var AudioPlayer = class extends import_node_events4.default {
|
||||
var AudioPlayer = class extends import_node_events4.EventEmitter {
|
||||
_state;
|
||||
subscribers = [];
|
||||
behaviors;
|
||||
debug;
|
||||
constructor(options = {}) {
|
||||
super();
|
||||
__publicField(this, '_state');
|
||||
__publicField(this, 'subscribers', []);
|
||||
__publicField(this, 'behaviors');
|
||||
__publicField(this, 'debug');
|
||||
this._state = { status: 'idle' /* Idle */ };
|
||||
this.behaviors = {
|
||||
noSubscriber: 'pause' /* Pause */,
|
||||
@ -1035,7 +1047,9 @@ to ${stringifyState2(newState)}`);
|
||||
_stepDispatch() {
|
||||
const state = this._state;
|
||||
if (state.status === 'idle' /* Idle */ || state.status === 'buffering' /* Buffering */) return;
|
||||
this.playable.forEach(connection => connection.dispatchAudio());
|
||||
for (const connection of this.playable) {
|
||||
connection.dispatchAudio();
|
||||
}
|
||||
}
|
||||
_stepPrepare() {
|
||||
const state = this._state;
|
||||
@ -1085,11 +1099,15 @@ to ${stringifyState2(newState)}`);
|
||||
}
|
||||
}
|
||||
_signalStopSpeaking() {
|
||||
return this.subscribers.forEach(({ connection }) => connection.setSpeaking(false));
|
||||
for (const { connection } of this.subscribers) {
|
||||
connection.setSpeaking(false);
|
||||
}
|
||||
}
|
||||
_preparePacket(packet, receivers, state) {
|
||||
state.playbackDuration += 20;
|
||||
receivers.forEach(connection => connection.prepareAudioPacket(packet));
|
||||
for (const connection of receivers) {
|
||||
connection.prepareAudioPacket(packet);
|
||||
}
|
||||
}
|
||||
};
|
||||
__name(AudioPlayer, 'AudioPlayer');
|
||||
@ -1114,24 +1132,23 @@ function createDefaultAudioReceiveStreamOptions() {
|
||||
}
|
||||
__name(createDefaultAudioReceiveStreamOptions, 'createDefaultAudioReceiveStreamOptions');
|
||||
var AudioReceiveStream = class extends import_node_stream.Readable {
|
||||
end;
|
||||
endTimeout;
|
||||
constructor({ end, ...options }) {
|
||||
super({
|
||||
...options,
|
||||
objectMode: true,
|
||||
});
|
||||
__publicField(this, 'end');
|
||||
__publicField(this, 'endTimeout');
|
||||
this.end = end;
|
||||
}
|
||||
push(buffer) {
|
||||
if (buffer) {
|
||||
if (
|
||||
this.end.behavior === 2 /* AfterInactivity */ ||
|
||||
if (
|
||||
buffer &&
|
||||
(this.end.behavior === 2 /* AfterInactivity */ ||
|
||||
(this.end.behavior === 1 /* AfterSilence */ &&
|
||||
(buffer.compare(SILENCE_FRAME) !== 0 || typeof this.endTimeout === 'undefined'))
|
||||
) {
|
||||
this.renewEndTimeout(this.end);
|
||||
}
|
||||
(buffer.compare(SILENCE_FRAME) !== 0 || typeof this.endTimeout === 'undefined')))
|
||||
) {
|
||||
this.renewEndTimeout(this.end);
|
||||
}
|
||||
return super.push(buffer);
|
||||
}
|
||||
@ -1148,9 +1165,9 @@ __name(AudioReceiveStream, 'AudioReceiveStream');
|
||||
// src/receive/SSRCMap.ts
|
||||
var import_node_events5 = require('events');
|
||||
var SSRCMap = class extends import_node_events5.EventEmitter {
|
||||
map;
|
||||
constructor() {
|
||||
super();
|
||||
__publicField(this, 'map');
|
||||
this.map = /* @__PURE__ */ new Map();
|
||||
}
|
||||
update(data) {
|
||||
@ -1198,10 +1215,10 @@ __name(SSRCMap, 'SSRCMap');
|
||||
// src/receive/SpeakingMap.ts
|
||||
var import_node_events6 = require('events');
|
||||
var _SpeakingMap = class extends import_node_events6.EventEmitter {
|
||||
users;
|
||||
speakingTimeouts;
|
||||
constructor() {
|
||||
super();
|
||||
__publicField(this, 'users');
|
||||
__publicField(this, 'speakingTimeouts');
|
||||
this.users = /* @__PURE__ */ new Map();
|
||||
this.speakingTimeouts = /* @__PURE__ */ new Map();
|
||||
}
|
||||
@ -1232,12 +1249,12 @@ __publicField(SpeakingMap, 'DELAY', 100);
|
||||
|
||||
// src/receive/VoiceReceiver.ts
|
||||
var VoiceReceiver = class {
|
||||
voiceConnection;
|
||||
ssrcMap;
|
||||
subscriptions;
|
||||
connectionData;
|
||||
speaking;
|
||||
constructor(voiceConnection) {
|
||||
__publicField(this, 'voiceConnection');
|
||||
__publicField(this, 'ssrcMap');
|
||||
__publicField(this, 'subscriptions');
|
||||
__publicField(this, 'connectionData');
|
||||
__publicField(this, 'speaking');
|
||||
this.voiceConnection = voiceConnection;
|
||||
this.ssrcMap = new SSRCMap();
|
||||
this.speaking = new SpeakingMap();
|
||||
@ -1280,7 +1297,7 @@ var VoiceReceiver = class {
|
||||
}
|
||||
const decrypted = methods.open(buffer.slice(12, end), nonce2, secretKey);
|
||||
if (!decrypted) return;
|
||||
return Buffer.from(decrypted);
|
||||
return import_node_buffer5.Buffer.from(decrypted);
|
||||
}
|
||||
parsePacket(buffer, mode, nonce2, secretKey) {
|
||||
let packet = this.decrypt(buffer, mode, nonce2, secretKey);
|
||||
@ -1329,11 +1346,11 @@ __name(VoiceReceiver, 'VoiceReceiver');
|
||||
|
||||
// src/VoiceConnection.ts
|
||||
var VoiceConnectionStatus = /* @__PURE__ */ (VoiceConnectionStatus2 => {
|
||||
VoiceConnectionStatus2['Signalling'] = 'signalling';
|
||||
VoiceConnectionStatus2['Connecting'] = 'connecting';
|
||||
VoiceConnectionStatus2['Ready'] = 'ready';
|
||||
VoiceConnectionStatus2['Disconnected'] = 'disconnected';
|
||||
VoiceConnectionStatus2['Destroyed'] = 'destroyed';
|
||||
VoiceConnectionStatus2['Disconnected'] = 'disconnected';
|
||||
VoiceConnectionStatus2['Ready'] = 'ready';
|
||||
VoiceConnectionStatus2['Signalling'] = 'signalling';
|
||||
return VoiceConnectionStatus2;
|
||||
})(VoiceConnectionStatus || {});
|
||||
var VoiceConnectionDisconnectReason = /* @__PURE__ */ (VoiceConnectionDisconnectReason2 => {
|
||||
@ -1343,23 +1360,23 @@ var VoiceConnectionDisconnectReason = /* @__PURE__ */ (VoiceConnectionDisconnect
|
||||
VoiceConnectionDisconnectReason2[(VoiceConnectionDisconnectReason2['Manual'] = 3)] = 'Manual';
|
||||
return VoiceConnectionDisconnectReason2;
|
||||
})(VoiceConnectionDisconnectReason || {});
|
||||
var VoiceConnection2 = class extends import_node_events7.EventEmitter {
|
||||
constructor(joinConfig, { debug, adapterCreator }) {
|
||||
var VoiceConnection = class extends import_node_events7.EventEmitter {
|
||||
rejoinAttempts;
|
||||
_state;
|
||||
joinConfig;
|
||||
packets;
|
||||
receiver;
|
||||
debug;
|
||||
constructor(joinConfig, options) {
|
||||
super();
|
||||
__publicField(this, 'rejoinAttempts');
|
||||
__publicField(this, '_state');
|
||||
__publicField(this, 'joinConfig');
|
||||
__publicField(this, 'packets');
|
||||
__publicField(this, 'receiver');
|
||||
__publicField(this, 'debug');
|
||||
this.debug = debug ? message => this.emit('debug', message) : null;
|
||||
this.debug = options.debug ? message => this.emit('debug', message) : null;
|
||||
this.rejoinAttempts = 0;
|
||||
this.receiver = new VoiceReceiver(this);
|
||||
this.onNetworkingClose = this.onNetworkingClose.bind(this);
|
||||
this.onNetworkingStateChange = this.onNetworkingStateChange.bind(this);
|
||||
this.onNetworkingError = this.onNetworkingError.bind(this);
|
||||
this.onNetworkingDebug = this.onNetworkingDebug.bind(this);
|
||||
const adapter = adapterCreator({
|
||||
const adapter = options.adapterCreator({
|
||||
onVoiceServerUpdate: data => this.addServerPacket(data),
|
||||
onVoiceStateUpdate: data => this.addStatePacket(data),
|
||||
destroy: () => this.destroy(false),
|
||||
@ -1449,7 +1466,7 @@ var VoiceConnection2 = class extends import_node_events7.EventEmitter {
|
||||
const networking = new Networking(
|
||||
{
|
||||
endpoint: server.endpoint,
|
||||
serverId: server.guild_id,
|
||||
serverId: server.guild_id ?? server.channel_id,
|
||||
token: server.token,
|
||||
sessionId: state.session_id,
|
||||
userId: state.user_id,
|
||||
@ -1621,7 +1638,7 @@ var VoiceConnection2 = class extends import_node_events7.EventEmitter {
|
||||
}
|
||||
}
|
||||
};
|
||||
__name(VoiceConnection2, 'VoiceConnection');
|
||||
__name(VoiceConnection, 'VoiceConnection');
|
||||
function createVoiceConnection(joinConfig, options) {
|
||||
const payload = createJoinVoiceChannelPayload(joinConfig);
|
||||
const existing = getVoiceConnection(joinConfig.guildId, joinConfig.group);
|
||||
@ -1631,6 +1648,7 @@ function createVoiceConnection(joinConfig, options) {
|
||||
channelId: joinConfig.channelId,
|
||||
selfDeaf: joinConfig.selfDeaf,
|
||||
selfMute: joinConfig.selfMute,
|
||||
selfVideo: joinConfig.selfVideo,
|
||||
});
|
||||
} else if (!existing.state.adapter.sendPayload(payload)) {
|
||||
existing.state = {
|
||||
@ -1641,16 +1659,17 @@ function createVoiceConnection(joinConfig, options) {
|
||||
}
|
||||
return existing;
|
||||
}
|
||||
const voiceConnection = new VoiceConnection2(joinConfig, options);
|
||||
const voiceConnection = new VoiceConnection(joinConfig, options);
|
||||
trackVoiceConnection(voiceConnection);
|
||||
if (voiceConnection.state.status !== 'destroyed' /* Destroyed */) {
|
||||
if (!voiceConnection.state.adapter.sendPayload(payload)) {
|
||||
voiceConnection.state = {
|
||||
...voiceConnection.state,
|
||||
status: 'disconnected' /* Disconnected */,
|
||||
reason: 1 /* AdapterUnavailable */,
|
||||
};
|
||||
}
|
||||
if (
|
||||
voiceConnection.state.status !== 'destroyed' /* Destroyed */ &&
|
||||
!voiceConnection.state.adapter.sendPayload(payload)
|
||||
) {
|
||||
voiceConnection.state = {
|
||||
...voiceConnection.state,
|
||||
status: 'disconnected' /* Disconnected */,
|
||||
reason: 1 /* AdapterUnavailable */,
|
||||
};
|
||||
}
|
||||
return voiceConnection;
|
||||
}
|
||||
@ -1662,6 +1681,7 @@ function joinVoiceChannel(options) {
|
||||
selfDeaf: true,
|
||||
selfMute: false,
|
||||
group: 'default',
|
||||
selfVideo: false,
|
||||
...options,
|
||||
};
|
||||
return createVoiceConnection(joinConfig, {
|
||||
@ -1694,16 +1714,16 @@ var FFMPEG_OPUS_ARGUMENTS = [
|
||||
];
|
||||
var StreamType = /* @__PURE__ */ (StreamType2 => {
|
||||
StreamType2['Arbitrary'] = 'arbitrary';
|
||||
StreamType2['Raw'] = 'raw';
|
||||
StreamType2['OggOpus'] = 'ogg/opus';
|
||||
StreamType2['WebmOpus'] = 'webm/opus';
|
||||
StreamType2['Opus'] = 'opus';
|
||||
StreamType2['Raw'] = 'raw';
|
||||
StreamType2['WebmOpus'] = 'webm/opus';
|
||||
return StreamType2;
|
||||
})(StreamType || {});
|
||||
var Node = class {
|
||||
edges = [];
|
||||
type;
|
||||
constructor(type) {
|
||||
__publicField(this, 'edges', []);
|
||||
__publicField(this, 'type');
|
||||
this.type = type;
|
||||
}
|
||||
addEdge(edge) {
|
||||
@ -1788,9 +1808,9 @@ function findPath(from, constraints, goal = getNode('opus' /* Opus */), path = [
|
||||
if (from === goal && constraints(path)) {
|
||||
return { cost: 0 };
|
||||
} else if (depth === 0) {
|
||||
return { cost: Infinity };
|
||||
return { cost: Number.POSITIVE_INFINITY };
|
||||
}
|
||||
let currentBest = void 0;
|
||||
let currentBest;
|
||||
for (const edge of from.edges) {
|
||||
if (currentBest && edge.cost > currentBest.cost) continue;
|
||||
const next = findPath(edge.to, constraints, goal, [...path, edge], depth - 1);
|
||||
@ -1799,7 +1819,7 @@ function findPath(from, constraints, goal = getNode('opus' /* Opus */), path = [
|
||||
currentBest = { cost, edge, next };
|
||||
}
|
||||
}
|
||||
return currentBest ?? { cost: Infinity };
|
||||
return currentBest ?? { cost: Number.POSITIVE_INFINITY };
|
||||
}
|
||||
__name(findPath, 'findPath');
|
||||
function constructPipeline(step) {
|
||||
@ -1819,17 +1839,17 @@ __name(findPipeline, 'findPipeline');
|
||||
|
||||
// src/audio/AudioResource.ts
|
||||
var AudioResource = class {
|
||||
playStream;
|
||||
edges;
|
||||
metadata;
|
||||
volume;
|
||||
encoder;
|
||||
audioPlayer;
|
||||
playbackDuration = 0;
|
||||
started = false;
|
||||
silencePaddingFrames;
|
||||
silenceRemaining = -1;
|
||||
constructor(edges, streams, metadata, silencePaddingFrames) {
|
||||
__publicField(this, 'playStream');
|
||||
__publicField(this, 'edges');
|
||||
__publicField(this, 'metadata');
|
||||
__publicField(this, 'volume');
|
||||
__publicField(this, 'encoder');
|
||||
__publicField(this, 'audioPlayer');
|
||||
__publicField(this, 'playbackDuration', 0);
|
||||
__publicField(this, 'started', false);
|
||||
__publicField(this, 'silencePaddingFrames');
|
||||
__publicField(this, 'silenceRemaining', -1);
|
||||
this.edges = edges;
|
||||
this.playStream = streams.length > 1 ? (0, import_node_stream2.pipeline)(streams, noop) : streams[0];
|
||||
this.metadata = metadata;
|
||||
@ -1921,7 +1941,7 @@ function findPackageJSON(dir, packageName, depth) {
|
||||
const pkg = require(attemptedPath);
|
||||
if (pkg.name !== packageName) throw new Error('package.json does not match');
|
||||
return pkg;
|
||||
} catch (err) {
|
||||
} catch {
|
||||
return findPackageJSON((0, import_node_path.resolve)(dir, '..'), packageName, depth - 1);
|
||||
}
|
||||
}
|
||||
@ -1933,7 +1953,7 @@ function version(name) {
|
||||
? require_package()
|
||||
: findPackageJSON((0, import_node_path.dirname)(require.resolve(name)), name, 3);
|
||||
return pkg?.version ?? 'not found';
|
||||
} catch (err) {
|
||||
} catch {
|
||||
return 'not found';
|
||||
}
|
||||
}
|
||||
@ -1960,7 +1980,7 @@ function generateDependencyReport() {
|
||||
const info = import_prism_media3.default.FFmpeg.getInfo();
|
||||
report.push(`- version: ${info.version}`);
|
||||
report.push(`- libopus: ${info.output.includes('--enable-libopus') ? 'yes' : 'no'}`);
|
||||
} catch (err) {
|
||||
} catch {
|
||||
report.push('- not found');
|
||||
}
|
||||
return ['-'.repeat(50), ...report, '-'.repeat(50)].join('\n');
|
||||
@ -1994,6 +2014,8 @@ async function entersState(target, status, timeoutOrSignal) {
|
||||
__name(entersState, 'entersState');
|
||||
|
||||
// src/util/demuxProbe.ts
|
||||
var import_node_buffer6 = require('buffer');
|
||||
var import_node_process = __toESM(require('process'));
|
||||
var import_node_stream3 = require('stream');
|
||||
var import_prism_media4 = __toESM(require('prism-media'));
|
||||
function validateDiscordOpusHead(opusHead) {
|
||||
@ -2002,12 +2024,18 @@ function validateDiscordOpusHead(opusHead) {
|
||||
return channels === 2 && sampleRate === 48e3;
|
||||
}
|
||||
__name(validateDiscordOpusHead, 'validateDiscordOpusHead');
|
||||
function demuxProbe(stream, probeSize = 1024, validator = validateDiscordOpusHead) {
|
||||
async function demuxProbe(stream, probeSize = 1024, validator = validateDiscordOpusHead) {
|
||||
return new Promise((resolve2, reject) => {
|
||||
if (stream.readableObjectMode) return reject(new Error('Cannot probe a readable stream in object mode'));
|
||||
if (stream.readableEnded) return reject(new Error('Cannot probe a stream that has ended'));
|
||||
let readBuffer = Buffer.alloc(0);
|
||||
let resolved = void 0;
|
||||
if (stream.readableObjectMode) {
|
||||
reject(new Error('Cannot probe a readable stream in object mode'));
|
||||
return;
|
||||
}
|
||||
if (stream.readableEnded) {
|
||||
reject(new Error('Cannot probe a stream that has ended'));
|
||||
return;
|
||||
}
|
||||
let readBuffer = import_node_buffer6.Buffer.alloc(0);
|
||||
let resolved;
|
||||
const finish = /* @__PURE__ */ __name(type => {
|
||||
stream.off('data', onData);
|
||||
stream.off('close', onClose);
|
||||
@ -2049,13 +2077,13 @@ function demuxProbe(stream, probeSize = 1024, validator = validateDiscordOpusHea
|
||||
}
|
||||
}, 'onClose');
|
||||
const onData = /* @__PURE__ */ __name(buffer => {
|
||||
readBuffer = Buffer.concat([readBuffer, buffer]);
|
||||
readBuffer = import_node_buffer6.Buffer.concat([readBuffer, buffer]);
|
||||
webm.write(buffer);
|
||||
ogg.write(buffer);
|
||||
if (readBuffer.length >= probeSize) {
|
||||
stream.off('data', onData);
|
||||
stream.pause();
|
||||
process.nextTick(onClose);
|
||||
import_node_process.default.nextTick(onClose);
|
||||
}
|
||||
}, 'onData');
|
||||
stream.once('error', reject);
|
||||
@ -2094,6 +2122,5 @@ __name(demuxProbe, 'demuxProbe');
|
||||
getVoiceConnections,
|
||||
joinVoiceChannel,
|
||||
validateDiscordOpusHead,
|
||||
Networking,
|
||||
});
|
||||
//# sourceMappingURL=index.js.map
|
||||
|
5
typings/enums.d.ts
vendored
5
typings/enums.d.ts
vendored
@ -112,6 +112,11 @@ export const enum ChannelTypes {
|
||||
GUILD_FORUM = 15,
|
||||
}
|
||||
|
||||
export const enum SortOrderType {
|
||||
LATEST_ACTIVITY = 1,
|
||||
CREATION_DATE = 2,
|
||||
}
|
||||
|
||||
export const enum MessageTypes {
|
||||
DEFAULT,
|
||||
RECIPIENT_ADD,
|
||||
|
214
typings/index.d.ts
vendored
214
typings/index.d.ts
vendored
@ -96,6 +96,7 @@ import {
|
||||
GuildScheduledEventPrivacyLevels,
|
||||
HypeSquadType,
|
||||
VideoQualityModes,
|
||||
SortOrderType,
|
||||
} from './enums';
|
||||
import {
|
||||
RawActivityData,
|
||||
@ -508,8 +509,8 @@ export class ApplicationCommand<PermissionsFetchType = {}> extends Base {
|
||||
message: Message,
|
||||
subCommandArray?: string[],
|
||||
options?: string[],
|
||||
): Promise<InteractionResponseBody>;
|
||||
public static sendContextMenu(message: Message): Promise<InteractionResponseBody>;
|
||||
): Promise<InteractionResponse>;
|
||||
public static sendContextMenu(message: Message): Promise<InteractionResponse>;
|
||||
}
|
||||
|
||||
export type ApplicationResolvable = Application | Activity | Snowflake;
|
||||
@ -674,7 +675,7 @@ export class BaseGuildTextChannel extends TextBasedChannelMixin(GuildChannel) {
|
||||
public defaultAutoArchiveDuration?: ThreadAutoArchiveDuration;
|
||||
public rateLimitPerUser: number | null;
|
||||
public nsfw: boolean;
|
||||
public threads: ThreadManager<AllowedThreadTypeForTextChannel | AllowedThreadTypeForNewsChannel>;
|
||||
public threads: GuildTextThreadManager<AllowedThreadTypeForTextChannel | AllowedThreadTypeForNewsChannel>;
|
||||
public topic: string | null;
|
||||
public createInvite(options?: CreateInviteOptions): Promise<Invite>;
|
||||
public fetchInvites(cache?: boolean): Promise<Collection<string, Invite>>;
|
||||
@ -757,7 +758,7 @@ export type MappedChannelCategoryTypes = EnumValueMapped<
|
||||
GUILD_TEXT: TextChannel;
|
||||
GUILD_STORE: StoreChannel;
|
||||
GUILD_STAGE_VOICE: StageChannel;
|
||||
GUILD_FORUM: undefined;
|
||||
GUILD_FORUM: ForumChannel;
|
||||
}
|
||||
>;
|
||||
|
||||
@ -801,6 +802,7 @@ export abstract class Channel extends Base {
|
||||
public id: Snowflake;
|
||||
public readonly partial: false;
|
||||
public type: keyof typeof ChannelTypes;
|
||||
public flags: Readonly<ChannelFlags> | null;
|
||||
public delete(): Promise<this>;
|
||||
public fetch(force?: boolean): Promise<this>;
|
||||
public isText(): this is TextBasedChannel;
|
||||
@ -1193,6 +1195,7 @@ export class DMChannel extends TextBasedChannelMixin(Channel, [
|
||||
private constructor(client: Client, data?: RawDMChannelData);
|
||||
public recipient: User;
|
||||
public type: 'DM';
|
||||
public flags: Readonly<ChannelFlags>;
|
||||
public fetch(force?: boolean): Promise<this>;
|
||||
public readonly voiceAdapterCreator: InternalDiscordGatewayAdapterCreator;
|
||||
public call(options?: object): Promise<VoiceConnection>;
|
||||
@ -1272,7 +1275,7 @@ export class Guild extends AnonymousGuild {
|
||||
public vanityURLUses: number | null;
|
||||
public readonly voiceAdapterCreator: InternalDiscordGatewayAdapterCreator;
|
||||
public readonly voiceStates: VoiceStateManager;
|
||||
public readonly widgetChannel: TextChannel | null;
|
||||
public readonly widgetChannel: TextChannel | NewsChannel | VoiceBasedChannel | ForumChannel | null;
|
||||
public widgetChannelId: Snowflake | null;
|
||||
public widgetEnabled: boolean | null;
|
||||
public readonly maximumBitrate: number;
|
||||
@ -1416,6 +1419,7 @@ export abstract class GuildChannel extends Channel {
|
||||
public readonly position: number;
|
||||
public rawPosition: number;
|
||||
public type: Exclude<keyof typeof ChannelTypes, 'DM' | 'GROUP_DM' | 'UNKNOWN'>;
|
||||
public flags: Readonly<ChannelFlags>;
|
||||
public readonly viewable: boolean;
|
||||
public clone(options?: GuildChannelCloneOptions): Promise<this>;
|
||||
public delete(reason?: string): Promise<this>;
|
||||
@ -1900,6 +1904,7 @@ export class Message<Cached extends boolean = boolean> extends Base {
|
||||
public webhookId: Snowflake | null;
|
||||
public flags: Readonly<MessageFlags>;
|
||||
public reference: MessageReference | null;
|
||||
public position: number | null;
|
||||
public awaitMessageComponent<T extends MessageComponentTypeResolvable = 'ACTION_ROW'>(
|
||||
options?: AwaitMessageCollectorOptionsParams<T, Cached>,
|
||||
): Promise<MappedInteractionTypes<Cached>[T]>;
|
||||
@ -1929,10 +1934,10 @@ export class Message<Cached extends boolean = boolean> extends Base {
|
||||
// Added
|
||||
public markUnread(): Promise<boolean>;
|
||||
public markRead(): Promise<boolean>;
|
||||
public clickButton(button: MessageButton | string): Promise<InteractionResponseBody>;
|
||||
public selectMenu(menuID: string, options: string[]): Promise<InteractionResponseBody>;
|
||||
public selectMenu(options: string[]): Promise<InteractionResponseBody>;
|
||||
public contextMenu(botID: Snowflake, commandName: string): Promise<InteractionResponseBody>;
|
||||
public clickButton(button: MessageButton | string): Promise<InteractionResponse>;
|
||||
public selectMenu(menuID: string, options: string[]): Promise<InteractionResponse>;
|
||||
public selectMenu(options: string[]): Promise<InteractionResponse>;
|
||||
public contextMenu(botID: Snowflake, commandName: string): Promise<InteractionResponse>;
|
||||
}
|
||||
|
||||
export class MessageActionRow<
|
||||
@ -1996,7 +2001,7 @@ export class MessageButton extends BaseMessageComponent {
|
||||
public setStyle(style: MessageButtonStyleResolvable): this;
|
||||
public setURL(url: string): this;
|
||||
public toJSON(): APIButtonComponent;
|
||||
public click(message: Message): Promise<InteractionResponseBody>;
|
||||
public click(message: Message): Promise<InteractionResponse>;
|
||||
private static resolveStyle(style: MessageButtonStyleResolvable): MessageButtonStyle;
|
||||
}
|
||||
|
||||
@ -2236,7 +2241,7 @@ export class MessageSelectMenu extends BaseMessageComponent {
|
||||
...options: MessageSelectOptionData[] | MessageSelectOptionData[][]
|
||||
): this;
|
||||
public toJSON(): APISelectMenuComponent;
|
||||
public select(message: Message, values: string[]): Promise<InteractionResponseBody>;
|
||||
public select(message: Message, values: string[]): Promise<InteractionResponse>;
|
||||
}
|
||||
|
||||
// Todo
|
||||
@ -2248,6 +2253,7 @@ export class Modal {
|
||||
public application: object | null;
|
||||
public client: Client | null;
|
||||
public nonce: Snowflake | null;
|
||||
public readonly sendFromInteraction: InteractionResponse | null;
|
||||
public addComponents(
|
||||
...components: (
|
||||
| MessageActionRow<ModalActionRowComponent>
|
||||
@ -2271,10 +2277,16 @@ export class Modal {
|
||||
): this;
|
||||
public setTitle(title: string): this;
|
||||
public toJSON(): RawModalSubmitInteractionData;
|
||||
public reply(guildId: Snowflake, channelId: Snowflake, ...args: ModalReplyData[]): Promise<Snowflake>;
|
||||
public reply(data: ModalReplyData): Promise<InteractionResponse>;
|
||||
}
|
||||
|
||||
export interface ModalReplyData {
|
||||
guild?: GuildResolvable;
|
||||
channel?: TextChannelResolvable;
|
||||
data: TextInputComponentReplyData[];
|
||||
}
|
||||
|
||||
export interface TextInputComponentReplyData {
|
||||
customId: string;
|
||||
value: string;
|
||||
}
|
||||
@ -2328,7 +2340,7 @@ export class ModalSubmitInteraction<Cached extends CacheType = CacheType> extend
|
||||
}
|
||||
|
||||
export class NewsChannel extends BaseGuildTextChannel {
|
||||
public threads: ThreadManager<AllowedThreadTypeForNewsChannel>;
|
||||
public threads: GuildTextThreadManager<AllowedThreadTypeForNewsChannel>;
|
||||
public type: 'GUILD_NEWS';
|
||||
public addFollower(channel: TextChannelResolvable, reason?: string): Promise<NewsChannel>;
|
||||
}
|
||||
@ -2356,6 +2368,7 @@ export class PartialGroupDMChannel extends TextBasedChannelMixin(Channel, [
|
||||
public lastPinTimestamp: number | null;
|
||||
public owner: User | null;
|
||||
public ownerId: Snowflake | null;
|
||||
public flags: null;
|
||||
public iconURL(options?: StaticImageURLOptions): string | null;
|
||||
public addMember(user: User): Promise<PartialGroupDMChannel>;
|
||||
public removeMember(user: User): Promise<PartialGroupDMChannel>;
|
||||
@ -2655,7 +2668,9 @@ export class StageChannel extends BaseGuildVoiceChannel {
|
||||
public setTopic(topic: string): Promise<StageChannel>;
|
||||
}
|
||||
|
||||
export class DirectoryChannel extends Channel {}
|
||||
export class DirectoryChannel extends Channel {
|
||||
public flags: Readonly<ChannelFlags>;
|
||||
}
|
||||
|
||||
export class StageInstance extends Base {
|
||||
private constructor(client: Client, data: RawStageInstanceData, channel: StageChannel);
|
||||
@ -2829,7 +2844,7 @@ export class TeamMember extends Base {
|
||||
|
||||
export class TextChannel extends BaseGuildTextChannel {
|
||||
public rateLimitPerUser: number;
|
||||
public threads: ThreadManager<AllowedThreadTypeForTextChannel>;
|
||||
public threads: GuildTextThreadManager<AllowedThreadTypeForTextChannel>;
|
||||
public type: 'GUILD_TEXT';
|
||||
}
|
||||
|
||||
@ -2880,10 +2895,13 @@ export class ThreadChannel extends TextBasedChannelMixin(Channel, ['fetchWebhook
|
||||
public members: ThreadMemberManager;
|
||||
public name: string;
|
||||
public ownerId: Snowflake | null;
|
||||
public readonly parent: TextChannel | NewsChannel | null;
|
||||
public readonly parent: TextChannel | NewsChannel | ForumChannel | null;
|
||||
public parentId: Snowflake | null;
|
||||
public rateLimitPerUser: number | null;
|
||||
public type: ThreadChannelTypes;
|
||||
public flags: Readonly<ChannelFlags>;
|
||||
public appliedTags: Snowflake[];
|
||||
public totalMessageSent: number | null;
|
||||
public readonly unarchivable: boolean;
|
||||
public isPrivate(): this is this & {
|
||||
readonly createdTimestamp: number;
|
||||
@ -2909,6 +2927,7 @@ export class ThreadChannel extends TextBasedChannelMixin(Channel, ['fetchWebhook
|
||||
public setInvitable(invitable?: boolean, reason?: string): Promise<ThreadChannel>;
|
||||
public setLocked(locked?: boolean, reason?: string): Promise<ThreadChannel>;
|
||||
public setName(name: string, reason?: string): Promise<ThreadChannel>;
|
||||
public setAppliedTags(appliedTags: Snowflake[], reason?: string): Promise<ThreadChannel>;
|
||||
}
|
||||
|
||||
export class ThreadMember extends Base {
|
||||
@ -3290,7 +3309,7 @@ export class WelcomeChannel extends Base {
|
||||
public channelId: Snowflake;
|
||||
public guild: Guild | InviteGuild;
|
||||
public description: string;
|
||||
public readonly channel: TextChannel | NewsChannel | StoreChannel | null;
|
||||
public readonly channel: TextChannel | NewsChannel | StoreChannel | ForumChannel | null;
|
||||
public readonly emoji: GuildEmoji | Emoji;
|
||||
}
|
||||
|
||||
@ -3883,6 +3902,26 @@ export class MessageManager extends CachedManager<Snowflake, Message, MessageRes
|
||||
public search(options: MessageSearchOptions): Promise<MessageSearchResult>;
|
||||
}
|
||||
|
||||
export class InteractionManager extends CachedManager<Snowflake, Message, MessageResolvable> {
|
||||
private constructor(channel: TextBasedChannel, iterable?: Iterable<RawMessageData>);
|
||||
public channel: TextBasedChannel;
|
||||
public cache: Collection<Snowflake, Message>;
|
||||
}
|
||||
|
||||
export class InteractionResponse extends Base {
|
||||
private constructor(client: Client, data: object);
|
||||
public readonly channel: GuildTextBasedChannel | TextBasedChannel;
|
||||
public channelId: Snowflake;
|
||||
public readonly createdAt: Date;
|
||||
public createdTimestamp: number;
|
||||
public guildId: Snowflake | null;
|
||||
public readonly guild: Snowflake | null;
|
||||
public id: Snowflake;
|
||||
public nonce: Snowflake;
|
||||
public sendData: object;
|
||||
public awaitModal(time?: number): Modal;
|
||||
}
|
||||
|
||||
export interface MessageSearchOptions {
|
||||
author: Snowflake[];
|
||||
content: string;
|
||||
@ -3973,10 +4012,9 @@ export class StageInstanceManager extends CachedManager<Snowflake, StageInstance
|
||||
public delete(channel: StageChannelResolvable): Promise<void>;
|
||||
}
|
||||
|
||||
export class ThreadManager<AllowedThreadType> extends CachedManager<Snowflake, ThreadChannel, ThreadChannelResolvable> {
|
||||
private constructor(channel: TextChannel | NewsChannel, iterable?: Iterable<RawThreadChannelData>);
|
||||
export class ThreadManager extends CachedManager<Snowflake, ThreadChannel, ThreadChannelResolvable> {
|
||||
protected constructor(channel: TextChannel | NewsChannel, iterable?: Iterable<RawThreadChannelData>);
|
||||
public channel: TextChannel | NewsChannel;
|
||||
public create(options: ThreadCreateOptions<AllowedThreadType>): Promise<ThreadChannel>;
|
||||
public fetch(options: ThreadChannelResolvable, cacheOptions?: BaseFetchOptions): Promise<ThreadChannel | null>;
|
||||
public fetch(options?: FetchThreadsOptions, cacheOptions?: { cache?: boolean }): Promise<FetchedThreads>;
|
||||
public fetchArchived(options?: FetchArchivedThreadOptions, cache?: boolean): Promise<FetchedThreads>;
|
||||
@ -4052,6 +4090,7 @@ export interface TextBasedChannelFields extends PartialTextBasedChannelFields {
|
||||
lastPinTimestamp: number | null;
|
||||
readonly lastPinAt: Date | null;
|
||||
messages: MessageManager;
|
||||
interactions: InteractionManager;
|
||||
awaitMessageComponent<T extends MessageComponentTypeResolvable = 'ACTION_ROW'>(
|
||||
options?: AwaitMessageCollectorOptionsParams<T, true>,
|
||||
): Promise<MappedInteractionTypes[T]>;
|
||||
@ -4069,7 +4108,7 @@ export interface TextBasedChannelFields extends PartialTextBasedChannelFields {
|
||||
setNSFW(nsfw?: boolean, reason?: string): Promise<this>;
|
||||
fetchWebhooks(): Promise<Collection<Snowflake, Webhook>>;
|
||||
sendTyping(): Promise<void>;
|
||||
sendSlash(bot: UserResolvable, commandName: string, ...args: any): Promise<InteractionResponseBody>;
|
||||
sendSlash(bot: UserResolvable, commandName: string, ...args: any): Promise<InteractionResponse>;
|
||||
}
|
||||
|
||||
export function PartialWebhookMixin<T>(Base?: Constructable<T>): Constructable<T & PartialWebhookFields>;
|
||||
@ -4407,7 +4446,7 @@ export interface ClientEvents extends BaseClientEvents {
|
||||
userUpdate: [oldUser: User | PartialUser, newUser: User];
|
||||
userSettingsUpdate: [setting: RawUserSettingsData];
|
||||
voiceStateUpdate: [oldState: VoiceState, newState: VoiceState];
|
||||
webhookUpdate: [channel: TextChannel | NewsChannel | VoiceChannel];
|
||||
webhookUpdate: [channel: TextChannel | NewsChannel | VoiceChannel | ForumChannel];
|
||||
/** @deprecated Use interactionCreate instead */
|
||||
interaction: [interaction: Interaction];
|
||||
interactionCreate: [interaction: Interaction | { nonce: Snowflake; id: Snowflake }];
|
||||
@ -4963,6 +5002,10 @@ export interface CategoryCreateChannelOptions {
|
||||
rateLimitPerUser?: number;
|
||||
position?: number;
|
||||
rtcRegion?: string;
|
||||
videoQualityMode?: VideoQualityMode;
|
||||
availableTags?: GuildForumTagData[];
|
||||
defaultReactionEmoji?: DefaultReactionEmoji;
|
||||
defaultSortOrder?: SortOrderType;
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
@ -4987,6 +5030,11 @@ export interface ChannelData {
|
||||
defaultAutoArchiveDuration?: ThreadAutoArchiveDuration | 'MAX';
|
||||
rtcRegion?: string | null;
|
||||
videoQualityMode?: VideoQualityMode | null;
|
||||
availableTags?: GuildForumTagData[];
|
||||
defaultReactionEmoji?: DefaultReactionEmoji;
|
||||
defaultThreadRateLimitPerUser?: number;
|
||||
defaultSortOrder?: SortOrderType | null;
|
||||
flags?: ChannelFlagsResolvable;
|
||||
}
|
||||
|
||||
export interface ChannelLogsQueryOptions {
|
||||
@ -6009,11 +6057,32 @@ export type MessageComponentTypeResolvable = MessageComponentType | MessageCompo
|
||||
export interface MessageEditOptions {
|
||||
attachments?: MessageAttachment[];
|
||||
content?: string | null;
|
||||
embeds?: (MessageEmbed | MessageEmbedOptions | APIEmbed)[] | null;
|
||||
files?: (FileOptions | BufferResolvable | Stream | MessageAttachment)[];
|
||||
flags?: BitFieldResolvable<MessageFlagsString, number>;
|
||||
allowedMentions?: MessageMentionOptions;
|
||||
components?: (MessageActionRow | (Required<BaseMessageComponentOptions> & MessageActionRowOptions))[];
|
||||
id?: Snowflake | number;
|
||||
parentId?: Snowflake | number;
|
||||
type?: ExcludeEnum<
|
||||
typeof ChannelTypes,
|
||||
| 'DM'
|
||||
| 'GROUP_DM'
|
||||
| 'GUILD_NEWS'
|
||||
| 'GUILD_STORE'
|
||||
| 'UNKNOWN'
|
||||
| 'GUILD_NEWS_THREAD'
|
||||
| 'GUILD_PUBLIC_THREAD'
|
||||
| 'GUILD_PRIVATE_THREAD'
|
||||
| 'GUILD_STAGE_VOICE'
|
||||
>;
|
||||
name: string;
|
||||
topic?: string;
|
||||
nsfw?: boolean;
|
||||
bitrate?: number;
|
||||
userLimit?: number;
|
||||
rtcRegion?: string | null;
|
||||
videoQualityMode?: VideoQualityMode;
|
||||
permissionOverwrites?: PartialOverwriteData[];
|
||||
rateLimitPerUser?: number;
|
||||
availableTags?: GuildForumTagData[];
|
||||
defaultReactionEmoji?: DefaultReactionEmoji;
|
||||
defaultThreadRateLimitPerUser?: number;
|
||||
}
|
||||
|
||||
export interface MessageEmbedAuthor {
|
||||
@ -6566,9 +6635,13 @@ export type AnyChannel =
|
||||
| StoreChannel
|
||||
| TextChannel
|
||||
| ThreadChannel
|
||||
| VoiceChannel;
|
||||
| VoiceChannel
|
||||
| ForumChannel;
|
||||
|
||||
export type TextBasedChannel = Extract<AnyChannel, { messages: MessageManager }>;
|
||||
export type TextBasedChannel = Exclude<
|
||||
Extract<AnyChannel, { messages: MessageManager; interactions: InteractionManager }>,
|
||||
ForumChannel
|
||||
>;
|
||||
|
||||
export type TextBasedChannelTypes = TextBasedChannel['type'];
|
||||
|
||||
@ -6597,7 +6670,7 @@ export type GuildBasedChannel = Extract<AnyChannel, { guild: Guild }>;
|
||||
|
||||
export type NonThreadGuildBasedChannel = Exclude<GuildBasedChannel, ThreadChannel>;
|
||||
|
||||
export type GuildTextBasedChannel = Extract<GuildBasedChannel, TextBasedChannel>;
|
||||
export type GuildTextBasedChannel = Exclude<Extract<GuildBasedChannel, TextBasedChannel>, ForumChannel>;
|
||||
|
||||
export type TextChannelResolvable = Snowflake | TextChannel;
|
||||
|
||||
@ -6609,7 +6682,7 @@ export type ThreadChannelResolvable = ThreadChannel | Snowflake;
|
||||
|
||||
export type ThreadChannelTypes = 'GUILD_NEWS_THREAD' | 'GUILD_PUBLIC_THREAD' | 'GUILD_PRIVATE_THREAD';
|
||||
|
||||
export interface ThreadCreateOptions<AllowedThreadType> extends StartThreadOptions {
|
||||
export interface GuildTextThreadCreateOptions<AllowedThreadType> extends StartThreadOptions {
|
||||
startMessage?: MessageResolvable;
|
||||
type?: AllowedThreadType;
|
||||
invitable?: AllowedThreadType extends 'GUILD_PRIVATE_THREAD' | 12 ? boolean : never;
|
||||
@ -6623,6 +6696,7 @@ export interface ThreadEditData {
|
||||
rateLimitPerUser?: number;
|
||||
locked?: boolean;
|
||||
invitable?: boolean;
|
||||
threadName?: string;
|
||||
}
|
||||
|
||||
export type ThreadMemberFlagsString = '';
|
||||
@ -6731,7 +6805,7 @@ export interface WidgetChannel {
|
||||
|
||||
export interface WelcomeChannelData {
|
||||
description: string;
|
||||
channel: TextChannel | NewsChannel | StoreChannel | Snowflake;
|
||||
channel: TextChannel | NewsChannel | StoreChannel | ForumChannel | Snowflake;
|
||||
emoji?: EmojiIdentifierResolvable;
|
||||
}
|
||||
|
||||
@ -6834,4 +6908,80 @@ export type InternalDiscordGatewayAdapterCreator = (
|
||||
methods: InternalDiscordGatewayAdapterLibraryMethods,
|
||||
) => InternalDiscordGatewayAdapterImplementerMethods;
|
||||
|
||||
// GuildForum
|
||||
export type ChannelFlagsString = 'PINNED' | 'REQUIRE_TAG';
|
||||
export class ChannelFlags extends BitField<ChannelFlagsString> {
|
||||
public static FLAGS: Record<ChannelFlagsString, number>;
|
||||
public static resolve(bit?: BitFieldResolvable<ChannelFlagsString, number>): number;
|
||||
}
|
||||
|
||||
export interface GuildForumTagEmoji {
|
||||
id: Snowflake | null;
|
||||
name: string | null;
|
||||
}
|
||||
|
||||
export interface GuildForumTag {
|
||||
id: Snowflake;
|
||||
name: string;
|
||||
moderated: boolean;
|
||||
emoji: GuildForumTagEmoji | null;
|
||||
}
|
||||
|
||||
export type GuildForumTagData = Partial<GuildForumTag> & { name: string };
|
||||
|
||||
export interface DefaultReactionEmoji {
|
||||
id: Snowflake | null;
|
||||
name: string | null;
|
||||
}
|
||||
|
||||
export class ForumChannel extends TextBasedChannelMixin(GuildChannel, [
|
||||
'send',
|
||||
'lastMessage',
|
||||
'lastPinAt',
|
||||
'bulkDelete',
|
||||
'sendTyping',
|
||||
'createMessageCollector',
|
||||
'awaitMessages',
|
||||
'createMessageComponentCollector',
|
||||
'awaitMessageComponent',
|
||||
]) {
|
||||
public type: 'GUILD_FORUM';
|
||||
public threads: GuildForumThreadManager;
|
||||
public availableTags: GuildForumTag[];
|
||||
public defaultReactionEmoji: DefaultReactionEmoji | null;
|
||||
public defaultThreadRateLimitPerUser: number | null;
|
||||
public rateLimitPerUser: number | null;
|
||||
public defaultAutoArchiveDuration: ThreadAutoArchiveDuration | null;
|
||||
public nsfw: boolean;
|
||||
public topic: string | null;
|
||||
public setAvailableTags(tags: GuildForumTagData[], reason?: string): Promise<this>;
|
||||
public setDefaultReactionEmoji(emojiId: DefaultReactionEmoji | null, reason?: string): Promise<this>;
|
||||
public setDefaultThreadRateLimitPerUser(rateLimit: number, reason?: string): Promise<this>;
|
||||
public createInvite(options?: CreateInviteOptions): Promise<Invite>;
|
||||
public fetchInvites(cache?: boolean): Promise<Collection<string, Invite>>;
|
||||
public setDefaultAutoArchiveDuration(
|
||||
defaultAutoArchiveDuration: ThreadAutoArchiveDuration,
|
||||
reason?: string,
|
||||
): Promise<this>;
|
||||
public setTopic(topic: string | null, reason?: string): Promise<this>;
|
||||
public setDefaultSortOrder(defaultSortOrder: SortOrderType | null, reason?: string): Promise<this>;
|
||||
}
|
||||
|
||||
export class GuildTextThreadManager<AllowedThreadType> extends ThreadManager {
|
||||
public create(options: GuildTextThreadCreateOptions<AllowedThreadType>): Promise<ThreadChannel>;
|
||||
}
|
||||
|
||||
export class GuildForumThreadManager extends ThreadManager {
|
||||
public create(options: GuildForumThreadCreateOptions): Promise<ThreadChannel>;
|
||||
}
|
||||
|
||||
export type GuildForumThreadMessageCreateOptions = MessageOptions & Pick<MessageOptions, 'flags' | 'stickers'>;
|
||||
|
||||
export type ChannelFlagsResolvable = BitFieldResolvable<ChannelFlagsString, number>;
|
||||
|
||||
export interface GuildForumThreadCreateOptions extends StartThreadOptions {
|
||||
message: GuildForumThreadMessageCreateOptions | MessagePayload;
|
||||
appliedTags?: Snowflake[];
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
Loading…
Reference in New Issue
Block a user