mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 16:13:31 +01:00
feat: add soundboard (#10590)
* feat: add soundboard * types(PartialSoundboardSound): add `available` * feat(VoiceChannelEffect): add `soundboardSound` getter * types: improve return types * docs: requested changes * feat: support multiple audio file types * types(GuildSoundboardSoundCreateOptions): add `contentType` * types: add default and guild soundboard sound * fix: requested changes * docs: use `@fires` tag * docs: remove misleading tag * chore: requested changes and missing things * feat: add send soundboard sound options
This commit is contained in:
@@ -76,6 +76,7 @@
|
||||
"discord-api-types": "^0.38.1",
|
||||
"fast-deep-equal": "3.1.3",
|
||||
"lodash.snakecase": "4.1.1",
|
||||
"magic-bytes.js": "^1.10.0",
|
||||
"tslib": "^2.8.1",
|
||||
"undici": "7.8.0"
|
||||
},
|
||||
|
||||
@@ -19,6 +19,7 @@ const { ClientPresence } = require('../structures/ClientPresence.js');
|
||||
const { GuildPreview } = require('../structures/GuildPreview.js');
|
||||
const { GuildTemplate } = require('../structures/GuildTemplate.js');
|
||||
const { Invite } = require('../structures/Invite.js');
|
||||
const { SoundboardSound } = require('../structures/SoundboardSound.js');
|
||||
const { Sticker } = require('../structures/Sticker.js');
|
||||
const { StickerPack } = require('../structures/StickerPack.js');
|
||||
const { VoiceRegion } = require('../structures/VoiceRegion.js');
|
||||
@@ -536,6 +537,19 @@ class Client extends BaseClient {
|
||||
return new Collection(data.sticker_packs.map(stickerPack => [stickerPack.id, new StickerPack(this, stickerPack)]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the list of default soundboard sounds.
|
||||
* @returns {Promise<Collection<string, SoundboardSound>>}
|
||||
* @example
|
||||
* client.fetchDefaultSoundboardSounds()
|
||||
* .then(sounds => console.log(`Available soundboard sounds are: ${sounds.map(sound => sound.name).join(', ')}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async fetchDefaultSoundboardSounds() {
|
||||
const data = await this.rest.get(Routes.soundboardDefaultSounds());
|
||||
return new Collection(data.map(sound => [sound.sound_id, new SoundboardSound(this, sound)]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a guild preview from Discord, available for all guilds the bot is in and all Discoverable guilds.
|
||||
* @param {GuildResolvable} guild The guild to fetch the preview for
|
||||
|
||||
@@ -131,6 +131,10 @@ class Action {
|
||||
return this.getPayload({ user_id: id }, manager, id, Partials.ThreadMember, false);
|
||||
}
|
||||
|
||||
getSoundboardSound(data, guild) {
|
||||
return this.getPayload(data, guild.soundboardSounds, data.sound_id, Partials.SoundboardSound);
|
||||
}
|
||||
|
||||
spreadInjectedData(data) {
|
||||
return Object.fromEntries(Object.getOwnPropertySymbols(data).map(symbol => [symbol, data[symbol]]));
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ class ActionsManager {
|
||||
this.register(require('./GuildScheduledEventDelete.js').GuildScheduledEventDeleteAction);
|
||||
this.register(require('./GuildScheduledEventUserAdd.js').GuildScheduledEventUserAddAction);
|
||||
this.register(require('./GuildScheduledEventUserRemove.js').GuildScheduledEventUserRemoveAction);
|
||||
this.register(require('./GuildSoundboardSoundDelete.js').GuildSoundboardSoundDeleteAction);
|
||||
this.register(require('./GuildStickerCreate.js').GuildStickerCreateAction);
|
||||
this.register(require('./GuildStickerDelete.js').GuildStickerDeleteAction);
|
||||
this.register(require('./GuildStickerUpdate.js').GuildStickerUpdateAction);
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
const { Action } = require('./Action.js');
|
||||
const { Events } = require('../../util/Events.js');
|
||||
|
||||
class GuildSoundboardSoundDeleteAction extends Action {
|
||||
handle(data) {
|
||||
const guild = this.client.guilds.cache.get(data.guild_id);
|
||||
|
||||
if (!guild) return {};
|
||||
|
||||
const soundboardSound = this.getSoundboardSound(data, guild);
|
||||
|
||||
if (soundboardSound) {
|
||||
guild.soundboardSounds.cache.delete(soundboardSound.soundId);
|
||||
|
||||
/**
|
||||
* Emitted whenever a soundboard sound is deleted in a guild.
|
||||
* @event Client#guildSoundboardSoundDelete
|
||||
* @param {SoundboardSound} soundboardSound The soundboard sound that was deleted
|
||||
*/
|
||||
this.client.emit(Events.GuildSoundboardSoundDelete, soundboardSound);
|
||||
}
|
||||
|
||||
return { soundboardSound };
|
||||
}
|
||||
}
|
||||
|
||||
exports.GuildSoundboardSoundDeleteAction = GuildSoundboardSoundDeleteAction;
|
||||
@@ -0,0 +1,24 @@
|
||||
'use strict';
|
||||
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const { Events } = require('../../../util/Events.js');
|
||||
|
||||
module.exports = (client, { d: data }) => {
|
||||
const guild = client.guilds.cache.get(data.guild_id);
|
||||
|
||||
if (!guild) return;
|
||||
|
||||
const soundboardSounds = new Collection();
|
||||
|
||||
for (const soundboardSound of data.soundboard_sounds) {
|
||||
soundboardSounds.set(soundboardSound.sound_id, guild.soundboardSounds._add(soundboardSound));
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted whenever multiple guild soundboard sounds are updated.
|
||||
* @event Client#guildSoundboardSoundsUpdate
|
||||
* @param {Collection<Snowflake, SoundboardSound>} soundboardSounds The updated soundboard sounds
|
||||
* @param {Guild} guild The guild that the soundboard sounds are from
|
||||
*/
|
||||
client.emit(Events.GuildSoundboardSoundsUpdate, soundboardSounds, guild);
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
const { Events } = require('../../../util/Events.js');
|
||||
|
||||
module.exports = (client, { d: data }) => {
|
||||
const guild = client.guilds.cache.get(data.guild_id);
|
||||
|
||||
if (!guild) return;
|
||||
|
||||
const soundboardSound = guild.soundboardSounds._add(data);
|
||||
|
||||
/**
|
||||
* Emitted whenever a guild soundboard sound is created.
|
||||
* @event Client#guildSoundboardSoundCreate
|
||||
* @param {SoundboardSound} soundboardSound The created guild soundboard sound
|
||||
*/
|
||||
client.emit(Events.GuildSoundboardSoundCreate, soundboardSound);
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, { d: data }) => {
|
||||
client.actions.GuildSoundboardSoundDelete.handle(data);
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
|
||||
const { Events } = require('../../../util/Events.js');
|
||||
|
||||
module.exports = (client, { d: data }) => {
|
||||
const guild = client.guilds.cache.get(data.guild_id);
|
||||
|
||||
if (!guild) return;
|
||||
|
||||
const oldGuildSoundboardSound = guild.soundboardSounds.cache.get(data.sound_id)?._clone() ?? null;
|
||||
const newGuildSoundboardSound = guild.soundboardSounds._add(data);
|
||||
|
||||
/**
|
||||
* Emitted whenever a guild soundboard sound is updated.
|
||||
* @event Client#guildSoundboardSoundUpdate
|
||||
* @param {?SoundboardSound} oldGuildSoundboardSound The guild soundboard sound before the update
|
||||
* @param {SoundboardSound} newGuildSoundboardSound The guild soundboard sound after the update
|
||||
*/
|
||||
client.emit(Events.GuildSoundboardSoundUpdate, oldGuildSoundboardSound, newGuildSoundboardSound);
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
'use strict';
|
||||
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const { Events } = require('../../../util/Events.js');
|
||||
|
||||
module.exports = (client, { d: data }) => {
|
||||
const guild = client.guilds.cache.get(data.guild_id);
|
||||
|
||||
if (!guild) return;
|
||||
|
||||
const soundboardSounds = new Collection();
|
||||
|
||||
for (const soundboardSound of data.soundboard_sounds) {
|
||||
soundboardSounds.set(soundboardSound.sound_id, guild.soundboardSounds._add(soundboardSound));
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted whenever soundboard sounds are received (all soundboard sounds come from the same guild).
|
||||
* @event Client#soundboardSounds
|
||||
* @param {Collection<Snowflake, SoundboardSound>} soundboardSounds The sounds received
|
||||
* @param {Guild} guild The guild that the soundboard sounds are from
|
||||
*/
|
||||
client.emit(Events.SoundboardSounds, soundboardSounds, guild);
|
||||
};
|
||||
@@ -32,6 +32,10 @@ const PacketHandlers = Object.fromEntries([
|
||||
['GUILD_SCHEDULED_EVENT_UPDATE', require('./GUILD_SCHEDULED_EVENT_UPDATE.js')],
|
||||
['GUILD_SCHEDULED_EVENT_USER_ADD', require('./GUILD_SCHEDULED_EVENT_USER_ADD.js')],
|
||||
['GUILD_SCHEDULED_EVENT_USER_REMOVE', require('./GUILD_SCHEDULED_EVENT_USER_REMOVE.js')],
|
||||
['GUILD_SOUNDBOARD_SOUNDS_UPDATE', require('./GUILD_SOUNDBOARD_SOUNDS_UPDATE.js')],
|
||||
['GUILD_SOUNDBOARD_SOUND_CREATE', require('./GUILD_SOUNDBOARD_SOUND_CREATE.js')],
|
||||
['GUILD_SOUNDBOARD_SOUND_DELETE', require('./GUILD_SOUNDBOARD_SOUND_DELETE.js')],
|
||||
['GUILD_SOUNDBOARD_SOUND_UPDATE', require('./GUILD_SOUNDBOARD_SOUND_UPDATE.js')],
|
||||
['GUILD_STICKERS_UPDATE', require('./GUILD_STICKERS_UPDATE.js')],
|
||||
['GUILD_UPDATE', require('./GUILD_UPDATE.js')],
|
||||
['INTERACTION_CREATE', require('./INTERACTION_CREATE.js')],
|
||||
@@ -49,6 +53,7 @@ const PacketHandlers = Object.fromEntries([
|
||||
['MESSAGE_UPDATE', require('./MESSAGE_UPDATE.js')],
|
||||
['PRESENCE_UPDATE', require('./PRESENCE_UPDATE.js')],
|
||||
['READY', require('./READY.js')],
|
||||
['SOUNDBOARD_SOUNDS', require('./SOUNDBOARD_SOUNDS.js')],
|
||||
['STAGE_INSTANCE_CREATE', require('./STAGE_INSTANCE_CREATE.js')],
|
||||
['STAGE_INSTANCE_DELETE', require('./STAGE_INSTANCE_DELETE.js')],
|
||||
['STAGE_INSTANCE_UPDATE', require('./STAGE_INSTANCE_UPDATE.js')],
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
* @property {'GuildChannelUnowned'} GuildChannelUnowned
|
||||
* @property {'GuildOwned'} GuildOwned
|
||||
* @property {'GuildMembersTimeout'} GuildMembersTimeout
|
||||
* @property {'GuildSoundboardSoundsTimeout'} GuildSoundboardSoundsTimeout
|
||||
* @property {'GuildUncachedMe'} GuildUncachedMe
|
||||
* @property {'ChannelNotCached'} ChannelNotCached
|
||||
* @property {'StageChannelResolve'} StageChannelResolve
|
||||
@@ -85,6 +86,8 @@
|
||||
* @property {'EmojiManaged'} EmojiManaged
|
||||
* @property {'MissingManageGuildExpressionsPermission'} MissingManageGuildExpressionsPermission
|
||||
*
|
||||
|
||||
* @property {'NotGuildSoundboardSound'} NotGuildSoundboardSound
|
||||
* @property {'NotGuildSticker'} NotGuildSticker
|
||||
|
||||
* @property {'ReactionResolveUser'} ReactionResolveUser
|
||||
@@ -193,6 +196,7 @@ const keys = [
|
||||
'GuildChannelUnowned',
|
||||
'GuildOwned',
|
||||
'GuildMembersTimeout',
|
||||
'GuildSoundboardSoundsTimeout',
|
||||
'GuildUncachedMe',
|
||||
'ChannelNotCached',
|
||||
'StageChannelResolve',
|
||||
@@ -217,6 +221,7 @@ const keys = [
|
||||
'EmojiManaged',
|
||||
'MissingManageGuildExpressionsPermission',
|
||||
|
||||
'NotGuildSoundboardSound',
|
||||
'NotGuildSticker',
|
||||
|
||||
'ReactionResolveUser',
|
||||
|
||||
@@ -66,6 +66,7 @@ const Messages = {
|
||||
[ErrorCodes.GuildChannelUnowned]: "The fetched channel does not belong to this manager's guild.",
|
||||
[ErrorCodes.GuildOwned]: 'Guild is owned by the client.',
|
||||
[ErrorCodes.GuildMembersTimeout]: "Members didn't arrive in time.",
|
||||
[ErrorCodes.GuildSoundboardSoundsTimeout]: "Soundboard sounds didn't arrive in time.",
|
||||
[ErrorCodes.GuildUncachedMe]: 'The client user as a member of this guild is uncached.',
|
||||
[ErrorCodes.ChannelNotCached]: 'Could not find the channel where this message came from in the cache!',
|
||||
[ErrorCodes.StageChannelResolve]: 'Could not resolve channel to a stage channel.',
|
||||
@@ -91,6 +92,8 @@ const Messages = {
|
||||
[ErrorCodes.MissingManageGuildExpressionsPermission]: guild =>
|
||||
`Client must have Manage Guild Expressions permission in guild ${guild} to see emoji authors.`,
|
||||
|
||||
[ErrorCodes.NotGuildSoundboardSound]: action =>
|
||||
`Soundboard sound is a default (non-guild) soundboard sound and can't be ${action}.`,
|
||||
[ErrorCodes.NotGuildSticker]: 'Sticker is a standard (non-guild) sticker and has no author.',
|
||||
|
||||
[ErrorCodes.ReactionResolveUser]: "Couldn't resolve the user id to remove from the reaction.",
|
||||
|
||||
@@ -74,6 +74,7 @@ exports.GuildMemberManager = require('./managers/GuildMemberManager.js').GuildMe
|
||||
exports.GuildMemberRoleManager = require('./managers/GuildMemberRoleManager.js').GuildMemberRoleManager;
|
||||
exports.GuildMessageManager = require('./managers/GuildMessageManager.js').GuildMessageManager;
|
||||
exports.GuildScheduledEventManager = require('./managers/GuildScheduledEventManager.js').GuildScheduledEventManager;
|
||||
exports.GuildSoundboardSoundManager = require('./managers/GuildSoundboardSoundManager.js').GuildSoundboardSoundManager;
|
||||
exports.GuildStickerManager = require('./managers/GuildStickerManager.js').GuildStickerManager;
|
||||
exports.GuildTextThreadManager = require('./managers/GuildTextThreadManager.js').GuildTextThreadManager;
|
||||
exports.MessageManager = require('./managers/MessageManager.js').MessageManager;
|
||||
@@ -196,6 +197,7 @@ exports.Role = require('./structures/Role.js').Role;
|
||||
exports.RoleSelectMenuComponent = require('./structures/RoleSelectMenuComponent.js').RoleSelectMenuComponent;
|
||||
exports.RoleSelectMenuInteraction = require('./structures/RoleSelectMenuInteraction.js').RoleSelectMenuInteraction;
|
||||
exports.SKU = require('./structures/SKU.js').SKU;
|
||||
exports.SoundboardSound = require('./structures/SoundboardSound.js').SoundboardSound;
|
||||
exports.StageChannel = require('./structures/StageChannel.js').StageChannel;
|
||||
exports.StageInstance = require('./structures/StageInstance.js').StageInstance;
|
||||
exports.Sticker = require('./structures/Sticker.js').Sticker;
|
||||
|
||||
@@ -4,8 +4,9 @@ const process = require('node:process');
|
||||
const { setTimeout, clearTimeout } = require('node:timers');
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const { makeURLSearchParams } = require('@discordjs/rest');
|
||||
const { Routes, RouteBases } = require('discord-api-types/v10');
|
||||
const { GatewayOpcodes, Routes, RouteBases } = require('discord-api-types/v10');
|
||||
const { CachedManager } = require('./CachedManager.js');
|
||||
const { DiscordjsError, ErrorCodes } = require('../errors/index.js');
|
||||
const { ShardClientUtil } = require('../sharding/ShardClientUtil.js');
|
||||
const { Guild } = require('../structures/Guild.js');
|
||||
const { GuildChannel } = require('../structures/GuildChannel.js');
|
||||
@@ -282,6 +283,71 @@ class GuildManager extends CachedManager {
|
||||
return data.reduce((coll, guild) => coll.set(guild.id, new OAuth2Guild(this.client, guild)), new Collection());
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} FetchSoundboardSoundsOptions
|
||||
* @param {Snowflake[]} guildIds The ids of the guilds to fetch soundboard sounds for
|
||||
* @param {number} [time=10_000] The timeout for receipt of the soundboard sounds
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fetches soundboard sounds for the specified guilds.
|
||||
* @param {FetchSoundboardSoundsOptions} options The options for fetching soundboard sounds
|
||||
* @returns {Promise<Collection<Snowflake, Collection<Snowflake, SoundboardSound>>>}
|
||||
* @example
|
||||
* // Fetch soundboard sounds for multiple guilds
|
||||
* const soundboardSounds = await client.guilds.fetchSoundboardSounds({
|
||||
* guildIds: ['123456789012345678', '987654321098765432'],
|
||||
* })
|
||||
*
|
||||
* console.log(soundboardSounds.get('123456789012345678'));
|
||||
*/
|
||||
async fetchSoundboardSounds({ guildIds, time = 10_000 }) {
|
||||
const shardCount = await this.client.ws.getShardCount();
|
||||
const shardIds = Map.groupBy(guildIds, guildId => ShardClientUtil.shardIdForGuildId(guildId, shardCount));
|
||||
|
||||
for (const [shardId, shardGuildIds] of shardIds) {
|
||||
this.client.ws.send(shardId, {
|
||||
op: GatewayOpcodes.RequestSoundboardSounds,
|
||||
d: {
|
||||
guild_ids: shardGuildIds,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const remainingGuildIds = new Set(guildIds);
|
||||
|
||||
const fetchedSoundboardSounds = new Collection();
|
||||
|
||||
const handler = (soundboardSounds, guild) => {
|
||||
timeout.refresh();
|
||||
|
||||
if (!remainingGuildIds.has(guild.id)) return;
|
||||
|
||||
fetchedSoundboardSounds.set(guild.id, soundboardSounds);
|
||||
|
||||
remainingGuildIds.delete(guild.id);
|
||||
|
||||
if (remainingGuildIds.size === 0) {
|
||||
clearTimeout(timeout);
|
||||
this.client.removeListener(Events.SoundboardSounds, handler);
|
||||
this.client.decrementMaxListeners();
|
||||
|
||||
resolve(fetchedSoundboardSounds);
|
||||
}
|
||||
};
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
this.client.removeListener(Events.SoundboardSounds, handler);
|
||||
this.client.decrementMaxListeners();
|
||||
reject(new DiscordjsError(ErrorCodes.GuildSoundboardSoundsTimeout));
|
||||
}, time).unref();
|
||||
|
||||
this.client.incrementMaxListeners();
|
||||
this.client.on(Events.SoundboardSounds, handler);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Options used to set incident actions. Supplying `null` to any option will disable the action.
|
||||
* @typedef {Object} IncidentActionsEditOptions
|
||||
|
||||
192
packages/discord.js/src/managers/GuildSoundboardSoundManager.js
Normal file
192
packages/discord.js/src/managers/GuildSoundboardSoundManager.js
Normal file
@@ -0,0 +1,192 @@
|
||||
'use strict';
|
||||
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const { lazy } = require('@discordjs/util');
|
||||
const { Routes } = require('discord-api-types/v10');
|
||||
const { CachedManager } = require('./CachedManager.js');
|
||||
const { DiscordjsTypeError, ErrorCodes } = require('../errors/index.js');
|
||||
const { SoundboardSound } = require('../structures/SoundboardSound.js');
|
||||
const { resolveBase64, resolveFile } = require('../util/DataResolver.js');
|
||||
|
||||
const fileTypeMime = lazy(() => require('magic-bytes.js').filetypemime);
|
||||
|
||||
/**
|
||||
* Manages API methods for Soundboard Sounds and stores their cache.
|
||||
* @extends {CachedManager}
|
||||
*/
|
||||
class GuildSoundboardSoundManager extends CachedManager {
|
||||
constructor(guild, iterable) {
|
||||
super(guild.client, SoundboardSound, iterable);
|
||||
|
||||
/**
|
||||
* The guild this manager belongs to
|
||||
* @type {Guild}
|
||||
*/
|
||||
this.guild = guild;
|
||||
}
|
||||
|
||||
/**
|
||||
* The cache of Soundboard Sounds
|
||||
* @type {Collection<Snowflake, SoundboardSound>}
|
||||
* @name GuildSoundboardSoundManager#cache
|
||||
*/
|
||||
|
||||
_add(data, cache) {
|
||||
return super._add(data, cache, { extras: [this.guild], id: data.sound_id });
|
||||
}
|
||||
|
||||
/**
|
||||
* Data that resolves to give a SoundboardSound object. This can be:
|
||||
* * A SoundboardSound object
|
||||
* * A Snowflake
|
||||
* @typedef {SoundboardSound|Snowflake} SoundboardSoundResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolves a SoundboardSoundResolvable to a SoundboardSound object.
|
||||
* @method resolve
|
||||
* @memberof GuildSoundboardSoundManager
|
||||
* @instance
|
||||
* @param {SoundboardSoundResolvable} soundboardSound The SoundboardSound resolvable to identify
|
||||
* @returns {?SoundboardSound}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolves a {@link SoundboardSoundResolvable} to a {@link SoundboardSound} id.
|
||||
* @param {SoundboardSoundResolvable} soundboardSound The soundboard sound resolvable to resolve
|
||||
* @returns {?Snowflake}
|
||||
*/
|
||||
resolveId(soundboardSound) {
|
||||
if (soundboardSound instanceof this.holds) return soundboardSound.soundId;
|
||||
if (typeof soundboardSound === 'string') return soundboardSound;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options used to create a soundboard sound in a guild.
|
||||
* @typedef {Object} GuildSoundboardSoundCreateOptions
|
||||
* @property {BufferResolvable|Stream} file The file for the soundboard sound
|
||||
* @property {string} name The name for the soundboard sound
|
||||
* @property {string} [contentType] The content type for the soundboard sound file
|
||||
* @property {number} [volume] The volume (a double) for the soundboard sound, from 0 (inclusive) to 1. Defaults to 1
|
||||
* @property {Snowflake} [emojiId] The emoji id for the soundboard sound
|
||||
* @property {string} [emojiName] The emoji name for the soundboard sound
|
||||
* @property {string} [reason] The reason for creating the soundboard sound
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a new guild soundboard sound.
|
||||
* @param {GuildSoundboardSoundCreateOptions} options Options for creating a guild soundboard sound
|
||||
* @returns {Promise<SoundboardSound>} The created soundboard sound
|
||||
* @example
|
||||
* // Create a new soundboard sound from a file on your computer
|
||||
* guild.soundboardSounds.create({ file: './sound.mp3', name: 'sound' })
|
||||
* .then(sound => console.log(`Created new soundboard sound with name ${sound.name}!`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async create({ contentType, emojiId, emojiName, file, name, reason, volume }) {
|
||||
const resolvedFile = await resolveFile(file);
|
||||
|
||||
const resolvedContentType = contentType ?? resolvedFile.contentType ?? fileTypeMime()(resolvedFile.data)[0];
|
||||
|
||||
const sound = resolveBase64(resolvedFile.data, resolvedContentType);
|
||||
|
||||
const body = { emoji_id: emojiId, emoji_name: emojiName, name, sound, volume };
|
||||
|
||||
const soundboardSound = await this.client.rest.post(Routes.guildSoundboardSounds(this.guild.id), {
|
||||
body,
|
||||
reason,
|
||||
});
|
||||
|
||||
return this._add(soundboardSound);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data for editing a soundboard sound.
|
||||
* @typedef {Object} GuildSoundboardSoundEditOptions
|
||||
* @property {string} [name] The name of the soundboard sound
|
||||
* @property {?number} [volume] The volume of the soundboard sound, from 0 to 1
|
||||
* @property {?Snowflake} [emojiId] The emoji id of the soundboard sound
|
||||
* @property {?string} [emojiName] The emoji name of the soundboard sound
|
||||
* @property {string} [reason] The reason for editing the soundboard sound
|
||||
*/
|
||||
|
||||
/**
|
||||
* Edits a soundboard sound.
|
||||
* @param {SoundboardSoundResolvable} soundboardSound The soundboard sound to edit
|
||||
* @param {GuildSoundboardSoundEditOptions} [options={}] The new data for the soundboard sound
|
||||
* @returns {Promise<SoundboardSound>}
|
||||
*/
|
||||
async edit(soundboardSound, options = {}) {
|
||||
const soundId = this.resolveId(soundboardSound);
|
||||
|
||||
if (!soundId) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'soundboardSound', 'SoundboardSoundResolvable');
|
||||
|
||||
const { emojiId, emojiName, name, reason, volume } = options;
|
||||
|
||||
const body = { emoji_id: emojiId, emoji_name: emojiName, name, volume };
|
||||
|
||||
const data = await this.client.rest.patch(Routes.guildSoundboardSound(this.guild.id, soundId), {
|
||||
body,
|
||||
reason,
|
||||
});
|
||||
|
||||
const existing = this.cache.get(soundId);
|
||||
|
||||
if (existing) {
|
||||
const clone = existing._clone();
|
||||
|
||||
clone._patch(data);
|
||||
return clone;
|
||||
}
|
||||
|
||||
return this._add(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a soundboard sound.
|
||||
* @param {SoundboardSoundResolvable} soundboardSound The soundboard sound to delete
|
||||
* @param {string} [reason] Reason for deleting this soundboard sound
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async delete(soundboardSound, reason) {
|
||||
const soundId = this.resolveId(soundboardSound);
|
||||
|
||||
if (!soundId) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'soundboardSound', 'SoundboardSoundResolvable');
|
||||
|
||||
await this.client.rest.delete(Routes.guildSoundboardSound(this.guild.id, soundId), { reason });
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains one or more soundboard sounds from Discord, or the soundboard sound cache if they're already available.
|
||||
* @param {Snowflake} [id] The soundboard sound's id
|
||||
* @param {BaseFetchOptions} [options] Additional options for this fetch
|
||||
* @returns {Promise<SoundboardSound|Collection<Snowflake, SoundboardSound>>}
|
||||
* @example
|
||||
* // Fetch all soundboard sounds from the guild
|
||||
* guild.soundboardSounds.fetch()
|
||||
* .then(sounds => console.log(`There are ${sounds.size} soundboard sounds.`))
|
||||
* .catch(console.error);
|
||||
* @example
|
||||
* // Fetch a single soundboard sound
|
||||
* guild.soundboardSounds.fetch('222078108977594368')
|
||||
* .then(sound => console.log(`The soundboard sound name is: ${sound.name}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async fetch(id, { cache = true, force = false } = {}) {
|
||||
if (id) {
|
||||
if (!force) {
|
||||
const existing = this.cache.get(id);
|
||||
if (existing) return existing;
|
||||
}
|
||||
|
||||
const sound = await this.client.rest.get(Routes.guildSoundboardSound(this.guild.id, id));
|
||||
return this._add(sound, cache);
|
||||
}
|
||||
|
||||
const data = await this.client.rest.get(Routes.guildSoundboardSounds(this.guild.id));
|
||||
return new Collection(data.map(sound => [sound.sound_id, this._add(sound, cache)]));
|
||||
}
|
||||
}
|
||||
|
||||
exports.GuildSoundboardSoundManager = GuildSoundboardSoundManager;
|
||||
@@ -21,6 +21,7 @@ const { GuildEmojiManager } = require('../managers/GuildEmojiManager.js');
|
||||
const { GuildInviteManager } = require('../managers/GuildInviteManager.js');
|
||||
const { GuildMemberManager } = require('../managers/GuildMemberManager.js');
|
||||
const { GuildScheduledEventManager } = require('../managers/GuildScheduledEventManager.js');
|
||||
const { GuildSoundboardSoundManager } = require('../managers/GuildSoundboardSoundManager.js');
|
||||
const { GuildStickerManager } = require('../managers/GuildStickerManager.js');
|
||||
const { PresenceManager } = require('../managers/PresenceManager.js');
|
||||
const { RoleManager } = require('../managers/RoleManager.js');
|
||||
@@ -107,6 +108,12 @@ class Guild extends AnonymousGuild {
|
||||
*/
|
||||
this.autoModerationRules = new AutoModerationRuleManager(this);
|
||||
|
||||
/**
|
||||
* A manager of the soundboard sounds of this guild.
|
||||
* @type {GuildSoundboardSoundManager}
|
||||
*/
|
||||
this.soundboardSounds = new GuildSoundboardSoundManager(this);
|
||||
|
||||
if (!data) return;
|
||||
if (data.unavailable) {
|
||||
/**
|
||||
|
||||
@@ -34,7 +34,6 @@ const Targets = {
|
||||
Unknown: 'Unknown',
|
||||
};
|
||||
|
||||
// TODO: Add soundboard sounds when https://github.com/discordjs/discord.js/pull/10590 is merged
|
||||
/**
|
||||
* The target of a guild audit log entry. It can be one of:
|
||||
* * A guild
|
||||
@@ -52,10 +51,11 @@ const Targets = {
|
||||
* * An application command
|
||||
* * An auto moderation rule
|
||||
* * A guild onboarding prompt
|
||||
* * A soundboard sound
|
||||
* * An object with an id key if target was deleted or fake entity
|
||||
* * An object where the keys represent either the new value or the old value
|
||||
* @typedef {?(Object|Guild|BaseChannel|User|Role|Invite|Webhook|GuildEmoji|Integration|StageInstance|Sticker|
|
||||
* GuildScheduledEvent|ApplicationCommand|AutoModerationRule|GuildOnboardingPrompt)} AuditLogEntryTarget
|
||||
* GuildScheduledEvent|ApplicationCommand|AutoModerationRule|GuildOnboardingPrompt|SoundboardSound)} AuditLogEntryTarget
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -369,9 +369,8 @@ class GuildAuditLogsEntry {
|
||||
this.target = guild.roles.cache.get(data.target_id) ?? { id: data.target_id };
|
||||
} else if (targetType === Targets.Emoji) {
|
||||
this.target = guild.emojis.cache.get(data.target_id) ?? { id: data.target_id };
|
||||
// TODO: Uncomment after https://github.com/discordjs/discord.js/pull/10590 is merged
|
||||
// } else if (targetType === Targets.SoundboardSound) {
|
||||
// this.target = guild.soundboardSounds.cache.get(data.target_id) ?? { id: data.target_id };
|
||||
} else if (targetType === Targets.SoundboardSound) {
|
||||
this.target = guild.soundboardSounds.cache.get(data.target_id) ?? { id: data.target_id };
|
||||
} else if (data.target_id) {
|
||||
this.target = { id: data.target_id };
|
||||
}
|
||||
|
||||
204
packages/discord.js/src/structures/SoundboardSound.js
Normal file
204
packages/discord.js/src/structures/SoundboardSound.js
Normal file
@@ -0,0 +1,204 @@
|
||||
'use strict';
|
||||
|
||||
const { DiscordSnowflake } = require('@sapphire/snowflake');
|
||||
const { Base } = require('./Base.js');
|
||||
const { Emoji } = require('./Emoji.js');
|
||||
const { DiscordjsError, ErrorCodes } = require('../errors/index.js');
|
||||
|
||||
/**
|
||||
* Represents a soundboard sound.
|
||||
* @extends {Base}
|
||||
*/
|
||||
class SoundboardSound extends Base {
|
||||
constructor(client, data) {
|
||||
super(client);
|
||||
|
||||
/**
|
||||
* The id of this soundboard sound
|
||||
* @type {Snowflake|string}
|
||||
*/
|
||||
this.soundId = data.sound_id;
|
||||
|
||||
this._patch(data);
|
||||
}
|
||||
|
||||
_patch(data) {
|
||||
if ('available' in data) {
|
||||
/**
|
||||
* Whether this soundboard sound is available
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.available = data.available;
|
||||
} else {
|
||||
this.available ??= null;
|
||||
}
|
||||
|
||||
if ('name' in data) {
|
||||
/**
|
||||
* The name of this soundboard sound
|
||||
* @type {?string}
|
||||
*/
|
||||
this.name = data.name;
|
||||
} else {
|
||||
this.name ??= null;
|
||||
}
|
||||
|
||||
if ('volume' in data) {
|
||||
/**
|
||||
* The volume (a double) of this soundboard sound, from 0 to 1
|
||||
* @type {?number}
|
||||
*/
|
||||
this.volume = data.volume;
|
||||
} else {
|
||||
this.volume ??= null;
|
||||
}
|
||||
|
||||
if ('emoji_id' in data) {
|
||||
/**
|
||||
* The raw emoji data of this soundboard sound
|
||||
* @type {?Object}
|
||||
* @private
|
||||
*/
|
||||
this._emoji = {
|
||||
id: data.emoji_id,
|
||||
name: data.emoji_name,
|
||||
};
|
||||
} else {
|
||||
this._emoji ??= null;
|
||||
}
|
||||
|
||||
if ('guild_id' in data) {
|
||||
/**
|
||||
* The guild id of this soundboard sound
|
||||
* @type {?Snowflake}
|
||||
*/
|
||||
this.guildId = data.guild_id;
|
||||
} else {
|
||||
this.guildId ??= null;
|
||||
}
|
||||
|
||||
if ('user' in data) {
|
||||
/**
|
||||
* The user who created this soundboard sound
|
||||
* @type {?User}
|
||||
*/
|
||||
this.user = this.client.users._add(data.user);
|
||||
} else {
|
||||
this.user ??= null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The timestamp this soundboard sound was created at
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get createdTimestamp() {
|
||||
return DiscordSnowflake.timestampFrom(this.soundId);
|
||||
}
|
||||
|
||||
/**
|
||||
* The time this soundboard sound was created at
|
||||
* @type {Date}
|
||||
* @readonly
|
||||
*/
|
||||
get createdAt() {
|
||||
return new Date(this.createdTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* The emoji of this soundboard sound
|
||||
* @type {?Emoji}
|
||||
* @readonly
|
||||
*/
|
||||
get emoji() {
|
||||
if (!this._emoji) return null;
|
||||
|
||||
return this.guild?.emojis.cache.get(this._emoji.id) ?? new Emoji(this.client, this._emoji);
|
||||
}
|
||||
|
||||
/**
|
||||
* The guild this soundboard sound is part of
|
||||
* @type {?Guild}
|
||||
* @readonly
|
||||
*/
|
||||
get guild() {
|
||||
return this.client.guilds.resolve(this.guildId);
|
||||
}
|
||||
|
||||
/**
|
||||
* A link to this soundboard sound
|
||||
* @type {string}
|
||||
* @readonly
|
||||
*/
|
||||
get url() {
|
||||
return this.client.rest.cdn.soundboardSound(this.soundId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits this soundboard sound.
|
||||
* @param {GuildSoundboardSoundEditOptions} options The options to provide
|
||||
* @returns {Promise<SoundboardSound>}
|
||||
* @example
|
||||
* // Update the name of a soundboard sound
|
||||
* soundboardSound.edit({ name: 'new name' })
|
||||
* .then(sound => console.log(`Updated the name of the soundboard sound to ${sound.name}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async edit(options) {
|
||||
if (!this.guildId) throw new DiscordjsError(ErrorCodes.NotGuildSoundboardSound, 'edited');
|
||||
|
||||
return this.guild.soundboardSounds.edit(this, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes this soundboard sound.
|
||||
* @param {string} [reason] Reason for deleting this soundboard sound
|
||||
* @returns {Promise<SoundboardSound>}
|
||||
* @example
|
||||
* // Delete a soundboard sound
|
||||
* soundboardSound.delete()
|
||||
* .then(sound => console.log(`Deleted soundboard sound ${sound.name}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async delete(reason) {
|
||||
if (!this.guildId) throw new DiscordjsError(ErrorCodes.NotGuildSoundboardSound, 'deleted');
|
||||
|
||||
await this.guild.soundboardSounds.delete(this, reason);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this soundboard sound is the same as another one.
|
||||
* @param {SoundboardSound|APISoundboardSound} other The soundboard sound to compare it to
|
||||
* @returns {boolean}
|
||||
*/
|
||||
equals(other) {
|
||||
if (other instanceof SoundboardSound) {
|
||||
return (
|
||||
this.soundId === other.soundId &&
|
||||
this.available === other.available &&
|
||||
this.name === other.name &&
|
||||
this.volume === other.volume &&
|
||||
this.emojiId === other.emojiId &&
|
||||
this.emojiName === other.emojiName &&
|
||||
this.guildId === other.guildId &&
|
||||
this.user?.id === other.user?.id
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
this.soundId === other.sound_id &&
|
||||
this.available === other.available &&
|
||||
this.name === other.name &&
|
||||
this.volume === other.volume &&
|
||||
this.emojiId === other.emoji_id &&
|
||||
this.emojiName === other.emoji_name &&
|
||||
this.guildId === other.guild_id &&
|
||||
this.user?.id === other.user?.id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
exports.SoundboardSound = SoundboardSound;
|
||||
@@ -225,7 +225,7 @@ class Sticker extends Base {
|
||||
* @returns {Promise<Sticker>}
|
||||
* @param {string} [reason] Reason for deleting this sticker
|
||||
* @example
|
||||
* // Delete a message
|
||||
* // Delete a sticker
|
||||
* sticker.delete()
|
||||
* .then(sticker => console.log(`Deleted sticker ${sticker.name}`))
|
||||
* .catch(console.error);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const { PermissionFlagsBits } = require('discord-api-types/v10');
|
||||
const { PermissionFlagsBits, Routes } = require('discord-api-types/v10');
|
||||
const { BaseGuildVoiceChannel } = require('./BaseGuildVoiceChannel.js');
|
||||
|
||||
/**
|
||||
@@ -35,6 +35,26 @@ class VoiceChannel extends BaseGuildVoiceChannel {
|
||||
permissions.has(PermissionFlagsBits.Speak, false)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} SendSoundboardSoundOptions
|
||||
* @property {string} soundId The id of the soundboard sound to send
|
||||
* @property {string} [guildId] The id of the guild the soundboard sound is a part of
|
||||
*/
|
||||
|
||||
/**
|
||||
* Send a soundboard sound to a voice channel the user is connected to.
|
||||
* @param {SoundboardSound|SendSoundboardSoundOptions} sound The sound to send
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async sendSoundboardSound(sound) {
|
||||
await this.client.rest.post(Routes.sendSoundboardSound(this.id), {
|
||||
body: {
|
||||
sound_id: sound.soundId,
|
||||
source_guild_id: sound.guildId ?? undefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -64,6 +64,15 @@ class VoiceChannelEffect {
|
||||
get channel() {
|
||||
return this.guild.channels.cache.get(this.channelId) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The soundboard sound for soundboard effects.
|
||||
* @type {?SoundboardSound}
|
||||
* @readonly
|
||||
*/
|
||||
get soundboardSound() {
|
||||
return this.guild.soundboardSounds.cache.get(this.soundId) ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
exports.VoiceChannelEffect = VoiceChannelEffect;
|
||||
|
||||
@@ -113,13 +113,14 @@ async function resolveFile(resource) {
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolves a Base64Resolvable to a Base 64 image.
|
||||
* Resolves a Base64Resolvable to a Base 64 string.
|
||||
* @param {Base64Resolvable} data The base 64 resolvable you want to resolve
|
||||
* @returns {?string}
|
||||
* @param {string} [contentType='image/jpg'] The content type of the data
|
||||
* @returns {string}
|
||||
* @private
|
||||
*/
|
||||
function resolveBase64(data) {
|
||||
if (Buffer.isBuffer(data)) return `data:image/jpg;base64,${data.toString('base64')}`;
|
||||
function resolveBase64(data, contentType = 'image/jpg') {
|
||||
if (Buffer.isBuffer(data)) return `data:${contentType};base64,${data.toString('base64')}`;
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,10 @@
|
||||
* @property {string} GuildScheduledEventUpdate guildScheduledEventUpdate
|
||||
* @property {string} GuildScheduledEventUserAdd guildScheduledEventUserAdd
|
||||
* @property {string} GuildScheduledEventUserRemove guildScheduledEventUserRemove
|
||||
* @property {string} GuildSoundboardSoundCreate guildSoundboardSoundCreate
|
||||
* @property {string} GuildSoundboardSoundDelete guildSoundboardSoundDelete
|
||||
* @property {string} GuildSoundboardSoundsUpdate guildSoundboardSoundsUpdate
|
||||
* @property {string} GuildSoundboardSoundUpdate guildSoundboardSoundUpdate
|
||||
* @property {string} GuildStickerCreate stickerCreate
|
||||
* @property {string} GuildStickerDelete stickerDelete
|
||||
* @property {string} GuildStickerUpdate stickerUpdate
|
||||
@@ -61,6 +65,7 @@
|
||||
* @property {string} MessageReactionRemoveEmoji messageReactionRemoveEmoji
|
||||
* @property {string} MessageUpdate messageUpdate
|
||||
* @property {string} PresenceUpdate presenceUpdate
|
||||
* @property {string} SoundboardSounds soundboardSounds
|
||||
* @property {string} StageInstanceCreate stageInstanceCreate
|
||||
* @property {string} StageInstanceDelete stageInstanceDelete
|
||||
* @property {string} StageInstanceUpdate stageInstanceUpdate
|
||||
@@ -127,6 +132,10 @@ exports.Events = {
|
||||
GuildScheduledEventUpdate: 'guildScheduledEventUpdate',
|
||||
GuildScheduledEventUserAdd: 'guildScheduledEventUserAdd',
|
||||
GuildScheduledEventUserRemove: 'guildScheduledEventUserRemove',
|
||||
GuildSoundboardSoundCreate: 'guildSoundboardSoundCreate',
|
||||
GuildSoundboardSoundDelete: 'guildSoundboardSoundDelete',
|
||||
GuildSoundboardSoundsUpdate: 'guildSoundboardSoundsUpdate',
|
||||
GuildSoundboardSoundUpdate: 'guildSoundboardSoundUpdate',
|
||||
GuildStickerCreate: 'stickerCreate',
|
||||
GuildStickerDelete: 'stickerDelete',
|
||||
GuildStickerUpdate: 'stickerUpdate',
|
||||
@@ -147,6 +156,7 @@ exports.Events = {
|
||||
MessageReactionRemoveEmoji: 'messageReactionRemoveEmoji',
|
||||
MessageUpdate: 'messageUpdate',
|
||||
PresenceUpdate: 'presenceUpdate',
|
||||
SoundboardSounds: 'soundboardSounds',
|
||||
StageInstanceCreate: 'stageInstanceCreate',
|
||||
StageInstanceDelete: 'stageInstanceDelete',
|
||||
StageInstanceUpdate: 'stageInstanceUpdate',
|
||||
|
||||
@@ -28,6 +28,7 @@ const { createEnum } = require('./Enums.js');
|
||||
* @property {number} ThreadMember The partial to receive uncached thread members.
|
||||
* @property {number} Poll The partial to receive uncached polls.
|
||||
* @property {number} PollAnswer The partial to receive uncached poll answers.
|
||||
* @property {number} SoundboardSound The partial to receive uncached soundboard sounds.
|
||||
*/
|
||||
|
||||
// JSDoc for IntelliSense purposes
|
||||
@@ -45,4 +46,5 @@ exports.Partials = createEnum([
|
||||
'ThreadMember',
|
||||
'Poll',
|
||||
'PollAnswer',
|
||||
'SoundboardSound',
|
||||
]);
|
||||
|
||||
89
packages/discord.js/typings/index.d.ts
vendored
89
packages/discord.js/typings/index.d.ts
vendored
@@ -160,6 +160,7 @@ import {
|
||||
GatewayVoiceChannelEffectSendDispatchData,
|
||||
RESTAPIPoll,
|
||||
EntryPointCommandHandlerType,
|
||||
APISoundboardSound,
|
||||
} from 'discord-api-types/v10';
|
||||
import { ChildProcess } from 'node:child_process';
|
||||
import { Stream } from 'node:stream';
|
||||
@@ -894,6 +895,7 @@ export class Client<Ready extends boolean = boolean> extends BaseClient<ClientEv
|
||||
public fetchSticker(id: Snowflake): Promise<Sticker>;
|
||||
public fetchStickerPacks(options: { packId: Snowflake }): Promise<StickerPack>;
|
||||
public fetchStickerPacks(options?: StickerPackFetchOptions): Promise<Collection<Snowflake, StickerPack>>;
|
||||
public fetchDefaultSoundboardSounds(): Promise<Collection<string, DefaultSoundboardSound>>;
|
||||
public fetchWebhook(id: Snowflake, token?: string): Promise<Webhook>;
|
||||
public fetchGuildWidget(guild: GuildResolvable): Promise<Widget>;
|
||||
public generateInvite(options?: InviteGenerationOptions): string;
|
||||
@@ -1320,6 +1322,7 @@ export class Guild extends AnonymousGuild {
|
||||
public safetyAlertsChannelId: Snowflake | null;
|
||||
public scheduledEvents: GuildScheduledEventManager;
|
||||
public shardId: number;
|
||||
public soundboardSounds: GuildSoundboardSoundManager;
|
||||
public stageInstances: StageInstanceManager;
|
||||
public stickers: GuildStickerManager;
|
||||
public incidentsData: IncidentActions | null;
|
||||
@@ -3480,9 +3483,15 @@ export type ComponentData =
|
||||
| ModalActionRowComponentData
|
||||
| ActionRowData<MessageActionRowComponentData | ModalActionRowComponentData>;
|
||||
|
||||
export interface SendSoundboardSoundOptions {
|
||||
soundId: Snowflake;
|
||||
guildId?: Snowflake;
|
||||
}
|
||||
|
||||
export class VoiceChannel extends BaseGuildVoiceChannel {
|
||||
public get speakable(): boolean;
|
||||
public type: ChannelType.GuildVoice;
|
||||
public sendSoundboardSound(sound: SoundboardSound | SendSoundboardSoundOptions): Promise<void>;
|
||||
}
|
||||
|
||||
export class VoiceChannelEffect {
|
||||
@@ -3496,6 +3505,7 @@ export class VoiceChannelEffect {
|
||||
public soundId: Snowflake | number | null;
|
||||
public soundVolume: number | null;
|
||||
public get channel(): VoiceChannel | null;
|
||||
public get soundboardSound(): GuildSoundboardSound | null;
|
||||
}
|
||||
|
||||
export class VoiceRegion {
|
||||
@@ -3625,6 +3635,30 @@ export class WidgetMember extends Base {
|
||||
public activity: WidgetActivity | null;
|
||||
}
|
||||
|
||||
export type SoundboardSoundResolvable = SoundboardSound | Snowflake | string;
|
||||
|
||||
export class SoundboardSound extends Base {
|
||||
private constructor(client: Client<true>, data: APISoundboardSound);
|
||||
public name: string;
|
||||
public soundId: Snowflake | string;
|
||||
public volume: number;
|
||||
private _emoji: Omit<APIEmoji, 'animated'> | null;
|
||||
public guildId: Snowflake | null;
|
||||
public available: boolean;
|
||||
public user: User | null;
|
||||
public get createdAt(): Date;
|
||||
public get createdTimestamp(): number;
|
||||
public get emoji(): Emoji | null;
|
||||
public get guild(): Guild | null;
|
||||
public get url(): string;
|
||||
public edit(options?: GuildSoundboardSoundEditOptions): Promise<GuildSoundboardSound>;
|
||||
public delete(reason?: string): Promise<GuildSoundboardSound>;
|
||||
public equals(other: SoundboardSound | APISoundboardSound): boolean;
|
||||
}
|
||||
|
||||
export type DefaultSoundboardSound = SoundboardSound & { get guild(): null; guildId: null; soundId: string };
|
||||
export type GuildSoundboardSound = SoundboardSound & { get guild(): Guild; guildId: Snowflake; soundId: Snowflake };
|
||||
|
||||
export class WelcomeChannel extends Base {
|
||||
private constructor(guild: Guild, data: RawWelcomeChannelData);
|
||||
private _emoji: Omit<APIEmoji, 'animated'>;
|
||||
@@ -3739,6 +3773,7 @@ export enum DiscordjsErrorCodes {
|
||||
GuildChannelUnowned = 'GuildChannelUnowned',
|
||||
GuildOwned = 'GuildOwned',
|
||||
GuildMembersTimeout = 'GuildMembersTimeout',
|
||||
GuildSoundboardSoundsTimeout = 'GuildSoundboardSoundsTimeout',
|
||||
GuildUncachedMe = 'GuildUncachedMe',
|
||||
ChannelNotCached = 'ChannelNotCached',
|
||||
StageChannelResolve = 'StageChannelResolve',
|
||||
@@ -3762,6 +3797,7 @@ export enum DiscordjsErrorCodes {
|
||||
EmojiManaged = 'EmojiManaged',
|
||||
MissingManageGuildExpressionsPermission = 'MissingManageGuildExpressionsPermission',
|
||||
|
||||
NotGuildSoundboardSound = 'NotGuildSoundboardSound',
|
||||
NotGuildSticker = 'NotGuildSticker',
|
||||
|
||||
ReactionResolveUser = 'ReactionResolveUser',
|
||||
@@ -4135,11 +4171,19 @@ export class GuildEmojiRoleManager extends DataManager<Snowflake, Role, RoleReso
|
||||
): Promise<GuildEmoji>;
|
||||
}
|
||||
|
||||
export interface FetchSoundboardSoundsOptions {
|
||||
guildIds: readonly Snowflake[];
|
||||
time?: number;
|
||||
}
|
||||
|
||||
export class GuildManager extends CachedManager<Snowflake, Guild, GuildResolvable> {
|
||||
private constructor(client: Client<true>, iterable?: Iterable<RawGuildData>);
|
||||
public create(options: GuildCreateOptions): Promise<Guild>;
|
||||
public fetch(options: Snowflake | FetchGuildOptions): Promise<Guild>;
|
||||
public fetch(options?: FetchGuildsOptions): Promise<Collection<Snowflake, OAuth2Guild>>;
|
||||
public fetchSoundboardSounds(
|
||||
options: FetchSoundboardSoundsOptions,
|
||||
): Promise<Collection<Snowflake, Collection<Snowflake, GuildSoundboardSound>>>;
|
||||
public setIncidentActions(
|
||||
guild: GuildResolvable,
|
||||
incidentActions: IncidentActionsEditOptions,
|
||||
@@ -4231,6 +4275,36 @@ export class GuildScheduledEventManager extends CachedManager<
|
||||
): Promise<GuildScheduledEventManagerFetchSubscribersResult<Options>>;
|
||||
}
|
||||
|
||||
export interface GuildSoundboardSoundCreateOptions {
|
||||
file: BufferResolvable | Stream;
|
||||
name: string;
|
||||
contentType?: string;
|
||||
volume?: number;
|
||||
emojiId?: Snowflake;
|
||||
emojiName?: string;
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
export interface GuildSoundboardSoundEditOptions {
|
||||
name?: string;
|
||||
volume?: number | null;
|
||||
emojiId?: Snowflake | null;
|
||||
emojiName?: string | null;
|
||||
}
|
||||
|
||||
export class GuildSoundboardSoundManager extends CachedManager<Snowflake, SoundboardSound, SoundboardSoundResolvable> {
|
||||
private constructor(guild: Guild, iterable?: Iterable<APISoundboardSound>);
|
||||
public guild: Guild;
|
||||
public create(options: GuildSoundboardSoundCreateOptions): Promise<GuildSoundboardSound>;
|
||||
public edit(
|
||||
soundboardSound: SoundboardSoundResolvable,
|
||||
options: GuildSoundboardSoundEditOptions,
|
||||
): Promise<GuildSoundboardSound>;
|
||||
public delete(soundboardSound: SoundboardSoundResolvable): Promise<void>;
|
||||
public fetch(id: Snowflake, options?: BaseFetchOptions): Promise<GuildSoundboardSound>;
|
||||
public fetch(options?: BaseFetchOptions): Promise<Collection<Snowflake, GuildSoundboardSound>>;
|
||||
}
|
||||
|
||||
export class GuildStickerManager extends CachedManager<Snowflake, Sticker, StickerResolvable> {
|
||||
private constructor(guild: Guild, iterable?: Iterable<RawStickerData>);
|
||||
public guild: Guild;
|
||||
@@ -4545,7 +4619,8 @@ export type AllowedPartial =
|
||||
| GuildScheduledEvent
|
||||
| ThreadMember
|
||||
| Poll
|
||||
| PollAnswer;
|
||||
| PollAnswer
|
||||
| SoundboardSound;
|
||||
|
||||
export type AllowedThreadTypeForAnnouncementChannel = ChannelType.AnnouncementThread;
|
||||
|
||||
@@ -5100,6 +5175,9 @@ export interface ClientEventTypes {
|
||||
guildMembersChunk: [members: ReadonlyCollection<Snowflake, GuildMember>, guild: Guild, data: GuildMembersChunk];
|
||||
guildMemberUpdate: [oldMember: GuildMember | PartialGuildMember, newMember: GuildMember];
|
||||
guildUpdate: [oldGuild: Guild, newGuild: Guild];
|
||||
guildSoundboardSoundCreate: [soundboardSound: SoundboardSound];
|
||||
guildSoundboardSoundDelete: [soundboardSound: SoundboardSound | PartialSoundboardSound];
|
||||
guildSoundboardSoundUpdate: [oldSoundboardSound: SoundboardSound | null, newSoundboardSound: SoundboardSound];
|
||||
inviteCreate: [invite: Invite];
|
||||
inviteDelete: [invite: Invite];
|
||||
messageCreate: [message: OmitPartialGroupDMChannel<Message>];
|
||||
@@ -5167,6 +5245,7 @@ export interface ClientEventTypes {
|
||||
guildScheduledEventDelete: [guildScheduledEvent: GuildScheduledEvent | PartialGuildScheduledEvent];
|
||||
guildScheduledEventUserAdd: [guildScheduledEvent: GuildScheduledEvent | PartialGuildScheduledEvent, user: User];
|
||||
guildScheduledEventUserRemove: [guildScheduledEvent: GuildScheduledEvent | PartialGuildScheduledEvent, user: User];
|
||||
soundboardSounds: [soundboardSounds: ReadonlyCollection<Snowflake, SoundboardSound>, guild: Guild];
|
||||
}
|
||||
|
||||
export interface ClientFetchInviteOptions {
|
||||
@@ -5369,6 +5448,11 @@ export enum Events {
|
||||
GuildScheduledEventDelete = 'guildScheduledEventDelete',
|
||||
GuildScheduledEventUserAdd = 'guildScheduledEventUserAdd',
|
||||
GuildScheduledEventUserRemove = 'guildScheduledEventUserRemove',
|
||||
GuildSoundboardSoundCreate = 'guildSoundboardSoundCreate',
|
||||
GuildSoundboardSoundDelete = 'guildSoundboardSoundDelete',
|
||||
GuildSoundboardSoundUpdate = 'guildSoundboardSoundUpdate',
|
||||
GuildSoundboardSoundsUpdate = 'guildSoundboardSoundsUpdate',
|
||||
SoundboardSounds = 'soundboardSounds',
|
||||
}
|
||||
|
||||
export enum ShardEvents {
|
||||
@@ -6535,6 +6619,8 @@ export interface PartialGuildScheduledEvent
|
||||
|
||||
export interface PartialThreadMember extends Partialize<ThreadMember, 'flags' | 'joinedAt' | 'joinedTimestamp'> {}
|
||||
|
||||
export interface PartialSoundboardSound extends Partialize<SoundboardSound, 'available' | 'name' | 'volume'> {}
|
||||
|
||||
export interface PartialOverwriteData {
|
||||
id: Snowflake | number;
|
||||
type?: OverwriteType;
|
||||
@@ -6556,6 +6642,7 @@ export enum Partials {
|
||||
ThreadMember,
|
||||
Poll,
|
||||
PollAnswer,
|
||||
SoundboardSound,
|
||||
}
|
||||
|
||||
export interface PartialUser extends Partialize<User, 'username' | 'tag' | 'discriminator'> {}
|
||||
|
||||
@@ -142,6 +142,10 @@ test('teamIcon default', () => {
|
||||
expect(cdn.teamIcon(id, hash)).toEqual(`${baseCDN}/team-icons/${id}/${hash}.webp`);
|
||||
});
|
||||
|
||||
test('soundboardSound', () => {
|
||||
expect(cdn.soundboardSound(id)).toEqual(`${baseCDN}/soundboard-sounds/${id}`);
|
||||
});
|
||||
|
||||
test('makeURL throws on invalid size', () => {
|
||||
// @ts-expect-error: Invalid size
|
||||
expect(() => cdn.avatar(id, animatedHash, { size: 5 })).toThrow(RangeError);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable jsdoc/check-param-names */
|
||||
import { CDNRoutes } from 'discord-api-types/v10';
|
||||
import {
|
||||
ALLOWED_EXTENSIONS,
|
||||
ALLOWED_SIZES,
|
||||
@@ -288,6 +289,15 @@ export class CDN {
|
||||
return this.makeURL(`/guild-events/${scheduledEventId}/${coverHash}`, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a URL for a soundboard sound.
|
||||
*
|
||||
* @param soundId - The soundboard sound id
|
||||
*/
|
||||
public soundboardSound(soundId: string): string {
|
||||
return `${this.cdn}${CDNRoutes.soundboardSound(soundId)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the URL for the resource, checking whether or not `hash` starts with `a_` if `dynamic` is set to `true`.
|
||||
*
|
||||
|
||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -910,6 +910,9 @@ importers:
|
||||
lodash.snakecase:
|
||||
specifier: 4.1.1
|
||||
version: 4.1.1
|
||||
magic-bytes.js:
|
||||
specifier: ^1.10.0
|
||||
version: 1.10.0
|
||||
tslib:
|
||||
specifier: ^2.8.1
|
||||
version: 2.8.1
|
||||
|
||||
Reference in New Issue
Block a user