Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add soundboard #10590

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/discord.js/src/client/Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const ActionsManager = require('./actions/ActionsManager');
const ClientVoiceManager = require('./voice/ClientVoiceManager');
const PacketHandlers = require('./websocket/handlers');
const { DiscordjsError, DiscordjsTypeError, ErrorCodes } = require('../errors');
// TODO: Uncomment after finishing the manager
// const { BaseSoundboardSoundManager } = require('../managers/BaseSoundboardSoundManager');
const BaseGuildEmojiManager = require('../managers/BaseGuildEmojiManager');
const ChannelManager = require('../managers/ChannelManager');
const GuildManager = require('../managers/GuildManager');
Expand Down Expand Up @@ -176,6 +178,13 @@ class Client extends BaseClient {
*/
this.voice = new ClientVoiceManager(this);

// TODO: Uncomment after finishing the manager
// /**
// * The soundboard sound manager of the client
// * @type {BaseSoundboardSoundManager}
// */
// this.soundboardSounds = new BaseSoundboardSoundManager(this);

/**
* User that the client is logged in as
* @type {?ClientUser}
Expand Down
3 changes: 3 additions & 0 deletions packages/discord.js/src/client/actions/ActionsManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ class ActionsManager {
this.register(require('./GuildScheduledEventUpdate'));
this.register(require('./GuildScheduledEventUserAdd'));
this.register(require('./GuildScheduledEventUserRemove'));
this.register(require('./GuildSoundboardSoundCreate'));
this.register(require('./GuildSoundboardSoundDelete'));
this.register(require('./GuildSoundboardSoundUpdate'));
this.register(require('./GuildStickerCreate'));
this.register(require('./GuildStickerDelete'));
this.register(require('./GuildStickerUpdate'));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict';

const Action = require('./Action');
const Events = require('../../util/Events');

class GuildSoundboardSoundCreateAction extends Action {
handle(data) {
const guild = this.client.guilds.cache.get(data.guild_id);

let soundboardSound;

if (guild) {
const already = guild.soundboardSounds.cache.has(data.sound_id);

soundboardSound = guild.soundboardSounds._add(data);

/**
* Emitted whenever a soundboard sound is created in a guild.
* @event Client#guildSoundboardSoundCreate
* @param {SoundboardSound} soundboardSound The soundboard sound that was created
*/
if (!already) this.client.emit(Events.GuildSoundboardSoundCreate, soundboardSound);
}

return { soundboardSound };
}
}

module.exports = GuildSoundboardSoundCreateAction;
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict';

const Action = require('./Action');
const Events = require('../../util/Events');

class GuildSoundboardSoundDeleteAction extends Action {
handle(data) {
const guild = this.client.guilds.cache.get(data.guild_id);

let soundboardSound;

if (guild) {
soundboardSound = guild.soundboardSounds.cache._add(data, false);

guild.soundboardSounds.cache.delete(soundboardSound.id);

/**
* 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 };
}
}

module.exports = GuildSoundboardSoundDeleteAction;
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use strict';

const Action = require('./Action');
const Events = require('../../util/Events');

class GuildSoundboardSoundUpdateAction extends Action {
handle(data) {
const guild = this.client.guilds.cache.get(data.guild_id);

if (guild) {
let oldSoundboardSound = null;

const newSoundboardSound = guild.soundboardSounds.cache.get(data.sound_id);

if (newSoundboardSound) {
oldSoundboardSound = newSoundboardSound._update(data);

/**
* Emitted whenever a soundboard sound is updated in a guild.
* @event Client#guildSoundboardSoundUpdate
* @param {?SoundboardSound} oldSoundboardSound The soundboard sound before the update
* @param {SoundboardSound} newSoundboardSound The soundboard sound after the update
*/
this.client.emit(Events.GuildSoundboardSoundUpdate, oldSoundboardSound, newSoundboardSound);
}

return { oldSoundboardSound, newSoundboardSound };
}

return { oldSoundboardSound: null, newSoundboardSound: null };
}
}

module.exports = GuildSoundboardSoundUpdateAction;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict';

module.exports = (client, { d: data }) => {
client.actions.GuildSoundboardSoundCreate.handle(data);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict';

module.exports = (client, { d: data }) => {
client.actions.GuildSoundboardSoundDelete.handle(data);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict';

module.exports = (client, { d: data }) => {
client.actions.GuildSoundboardSoundUpdate.handle(data);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use strict';

const { Collection } = require('@discordjs/collection');
const Events = require('../../../util/Events');

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 related to the soundboard sounds
*/
client.emit(Events.SoundboardSounds, soundboardSounds, guild);
};
6 changes: 6 additions & 0 deletions packages/discord.js/src/client/websocket/handlers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ const handlers = Object.fromEntries([
['GUILD_SCHEDULED_EVENT_UPDATE', require('./GUILD_SCHEDULED_EVENT_UPDATE')],
['GUILD_SCHEDULED_EVENT_USER_ADD', require('./GUILD_SCHEDULED_EVENT_USER_ADD')],
['GUILD_SCHEDULED_EVENT_USER_REMOVE', require('./GUILD_SCHEDULED_EVENT_USER_REMOVE')],
['GUILD_SOUNDBOARD_SOUND_CREATE', require('./GUILD_SOUNDBOARD_SOUND_CREATE')],
['GUILD_SOUNDBOARD_SOUND_DELETE', require('./GUILD_SOUNDBOARD_SOUND_DELETE')],
['GUILD_SOUNDBOARD_SOUND_UPDATE', require('./GUILD_SOUNDBOARD_SOUND_UPDATE')],
// TODO: Uncomment this line after finishing the GUILD_SOUNDBOARD_SOUNDS_UPDATE handler
// ['GUILD_SOUNDBOARD_SOUNDS_UPDATE', require('./GUILD_SOUNDBOARD_SOUNDS_UPDATE')],
['GUILD_STICKERS_UPDATE', require('./GUILD_STICKERS_UPDATE')],
['GUILD_UPDATE', require('./GUILD_UPDATE')],
['INTERACTION_CREATE', require('./INTERACTION_CREATE')],
Expand All @@ -49,6 +54,7 @@ const handlers = Object.fromEntries([
['MESSAGE_UPDATE', require('./MESSAGE_UPDATE')],
['PRESENCE_UPDATE', require('./PRESENCE_UPDATE')],
['READY', require('./READY')],
['SOUNDBOARD_SOUNDS', require('./SOUNDBOARD_SOUNDS')],
['STAGE_INSTANCE_CREATE', require('./STAGE_INSTANCE_CREATE')],
['STAGE_INSTANCE_DELETE', require('./STAGE_INSTANCE_DELETE')],
['STAGE_INSTANCE_UPDATE', require('./STAGE_INSTANCE_UPDATE')],
Expand Down
5 changes: 5 additions & 0 deletions packages/discord.js/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ exports.ApplicationEmojiManager = require('./managers/ApplicationEmojiManager');
exports.ApplicationCommandPermissionsManager = require('./managers/ApplicationCommandPermissionsManager');
exports.AutoModerationRuleManager = require('./managers/AutoModerationRuleManager');
exports.BaseGuildEmojiManager = require('./managers/BaseGuildEmojiManager');
// TODO: Uncomment after finishing the manager
// exports.BaseSoundboardSoundManager = require('./managers/BaseSoundboardSoundManager').BaseSoundboardSoundManager;
exports.CachedManager = require('./managers/CachedManager');
exports.ChannelManager = require('./managers/ChannelManager');
exports.ClientVoiceManager = require('./client/voice/ClientVoiceManager');
Expand All @@ -73,6 +75,8 @@ exports.GuildManager = require('./managers/GuildManager');
exports.GuildMemberManager = require('./managers/GuildMemberManager');
exports.GuildMemberRoleManager = require('./managers/GuildMemberRoleManager');
exports.GuildMessageManager = require('./managers/GuildMessageManager');
// Uncomment after finishing the manager
// exports.GuildSoundboardSoundManager = require('./managers/GuildSoundboardSoundManager').GuildSoundboardSoundManager;
exports.GuildScheduledEventManager = require('./managers/GuildScheduledEventManager');
exports.GuildStickerManager = require('./managers/GuildStickerManager');
exports.GuildTextThreadManager = require('./managers/GuildTextThreadManager');
Expand Down Expand Up @@ -194,6 +198,7 @@ exports.RoleSelectMenuInteraction = require('./structures/RoleSelectMenuInteract
exports.StringSelectMenuInteraction = require('./structures/StringSelectMenuInteraction');
exports.UserSelectMenuInteraction = require('./structures/UserSelectMenuInteraction');
exports.SKU = require('./structures/SKU').SKU;
exports.SoundboardSound = require('./structures/SoundboardSound').SoundboardSound;
exports.StringSelectMenuOptionBuilder = require('./structures/StringSelectMenuOptionBuilder');
exports.StageChannel = require('./structures/StageChannel');
exports.StageInstance = require('./structures/StageInstance').StageInstance;
Expand Down
9 changes: 9 additions & 0 deletions packages/discord.js/src/structures/Guild.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ const VoiceStateManager = require('../managers/VoiceStateManager');
const { resolveImage } = require('../util/DataResolver');
const SystemChannelFlagsBitField = require('../util/SystemChannelFlagsBitField');
const { discordSort, getSortableGroupTypes, resolvePartialEmoji } = require('../util/Util');
// TODO: Uncomment this after finishing the manager
// const { GuildSoundboardSoundManager } = require('../managers/GuildSoundboardSoundManager');

/**
* Represents a guild (or a server) on Discord.
Expand Down Expand Up @@ -106,6 +108,13 @@ class Guild extends AnonymousGuild {
*/
this.autoModerationRules = new AutoModerationRuleManager(this);

// TODO: Remove this after finishing the manager
// /**
// * A manager of the soundboard sounds of this guild.
// * @type {GuildSoundboardSoundManager}
// */
// this.soundboardSounds = new GuildSoundboardSoundManager(this);

if (!data) return;
if (data.unavailable) {
/**
Expand Down
121 changes: 121 additions & 0 deletions packages/discord.js/src/structures/SoundboardSound.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
'use strict';

const Base = require('./Base');

/**
* Represents a soundboard sound.
* @extends {Base}
*/
class SoundboardSound extends Base {
constructor(client, data) {
super(client);

/**
* The id of the soundboard sound
* @type {Snowflake|number}
*/
this.soundId = data.sound_id;

this._patch(data);
}

_patch(data) {
/**
* Whether this soundboard sound is available
* @type {boolean}
*/
this.available = data.available;

/**
* The name of the soundboard sound
* @type {string}
*/
this.name = data.name;

/**
* The volume of the soundboard sound
* @type {number}
*/
this.volume = data.volume;

if ('emoji_id' in data) {
/**
* The emoji id of the soundboard sound
* @type {?Snowflake}
*/
this.emojiId = data.emojiId;
} else {
this.emojiId ??= null;
}

if ('emoji_name' in data) {
/**
* The emoji name of the soundboard sound
* @type {?string}
*/
this.emojiName = data.emojiName;
} else {
this.emojiName ??= null;
}

if ('guild_id' in data) {
/**
* The guild id of the soundboard sound
* @type {?Snowflake}
*/
this.guildId = data.guildId;
} 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 guild this soundboard sound is part of
* @type {?Guild}
* @readonly
*/
get guild() {
return this.client.guilds.resolve(this.guildId);
}

/**
* 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.id === other.id &&
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.id === other.sound_id &&
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;
17 changes: 16 additions & 1 deletion packages/discord.js/src/structures/VoiceChannel.js
Original file line number Diff line number Diff line change
@@ -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');

/**
Expand Down Expand Up @@ -35,6 +35,21 @@ class VoiceChannel extends BaseGuildVoiceChannel {
permissions.has(PermissionFlagsBits.Speak, false)
);
}

/**
* Send a soundboard sound to a voice channel the user is connected to.
* Fires a Voice Channel Effect Send Gateway event.
* @param {SoundboardSound} sound the sound to send
* @returns {void}
*/
async sendSoundboardSound(sound) {
await this.client.rest.post(Routes.sendSoundboardSound(this.id), {
body: {
sound_id: sound.id,
source_guild_id: sound.guildId ?? undefined,
},
});
}
}

/**
Expand Down
Loading
Loading