LOADING JASON DISCORD HOST V1
LOADING JASON DISCORD HOST V1
Jason Discord Host V1
This is a honest music skeleton: a per-guild queue, voice connection, and transport controls. It streams direct HTTP(S) audio. YouTube/Spotify extractors are omitted because they break constantly and often violate platform terms. FFmpeg must be installed on the host.
Enable: Guild Voice States.
require("dotenv").config();
const {
Client,
GatewayIntentBits,
EmbedBuilder,
SlashCommandBuilder,
ChannelType,
Events,
MessageFlags,
} = require("discord.js");
const {
joinVoiceChannel,
createAudioPlayer,
createAudioResource,
AudioPlayerStatus,
NoSubscriberBehavior,
VoiceConnectionStatus,
entersState,
} = require("@discordjs/voice");
const TOKEN = process.env.DISCORD_TOKEN;
const GUILD_ID = process.env.GUILD_ID?.trim();
if (!TOKEN) {
console.error("Missing DISCORD_TOKEN. Copy .env.example to .env and add your bot token.");
process.exit(1);
}
const client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates],
});
/** @type {Map<string, { queue: {url:string, title:string, requestedBy:string}[], player: import("@discordjs/voice").AudioPlayer, connection: import("@discordjs/voice").VoiceConnection, textChannelId: string }>} */
const sessions = new Map();
const commands = [
new SlashCommandBuilder().setName("ping").setDescription("Check that Music Bot is online."),
new SlashCommandBuilder()
.setName("play")
.setDescription("Queue a direct HTTPS audio URL (mp3/ogg/wav). YouTube/Spotify are not supported.")
.addStringOption((o) => o.setName("url").setDescription("Direct audio URL").setRequired(true)),
new SlashCommandBuilder().setName("skip").setDescription("Skip the current track."),
new SlashCommandBuilder().setName("queue").setDescription("Show the current queue."),
new SlashCommandBuilder().setName("pause").setDescription("Pause playback."),
new SlashCommandBuilder().setName("resume").setDescription("Resume playback."),
new SlashCommandBuilder().setName("stop").setDescription("Stop playback, clear the queue, and leave."),
new SlashCommandBuilder().setName("nowplaying").setDescription("Show the track that is playing."),
].map((c) => c.toJSON());
function isDirectAudioUrl(value) {
try {
const url = new URL(value);
if (url.protocol !== "https:" && url.protocol !== "http:") return false;
const host = url.hostname.toLowerCase();
if (host.includes("youtube.com") || host.includes("youtu.be") || host.includes("spotify.com")) {
return false;
}
return true;
} catch {
return false;
}
}
function getSession(guildId) {
return sessions.get(guildId);
}
function destroySession(guildId) {
const session = sessions.get(guildId);
if (!session) return;
try {
session.player.stop(true);
session.connection.destroy();
} catch {
// already closed
}
sessions.delete(guildId);
}
function playNext(guildId) {
const session = getSession(guildId);
if (!session) return;
const next = session.queue[0];
if (!next) {
destroySession(guildId);
return;
}
const resource = createAudioResource(next.url);
session.player.play(resource);
}
async function ensureSession(interaction) {
const member = interaction.member;
const voice = member.voice?.channel;
if (
!voice ||
(voice.type !== ChannelType.GuildVoice && voice.type !== ChannelType.GuildStageVoice)
) {
throw new Error("Join a voice channel first, then run /play.");
}
let session = getSession(interaction.guildId);
if (session) return session;
const connection = joinVoiceChannel({
channelId: voice.id,
guildId: interaction.guildId,
adapterCreator: interaction.guild.voiceAdapterCreator,
selfDeaf: true,
});
await entersState(connection, VoiceConnectionStatus.Ready, 15_000);
const player = createAudioPlayer({ behaviors: { noSubscriber: NoSubscriberBehavior.Pause } });
connection.subscribe(player);
session = {
queue: [],
player,
connection,
textChannelId: interaction.channelId,
};
sessions.set(interaction.guildId, session);
player.on(AudioPlayerStatus.Idle, () => {
const current = getSession(interaction.guildId);
if (!current) return;
current.queue.shift();
playNext(interaction.guildId);
});
player.on("error", (error) => {
console.error("Audio player error:", error);
const current = getSession(interaction.guildId);
if (!current) return;
current.queue.shift();
playNext(interaction.guildId);
});
connection.on(VoiceConnectionStatus.Disconnected, async () => {
try {
await Promise.race([
entersState(connection, VoiceConnectionStatus.Signalling, 5_000),
entersState(connection, VoiceConnectionStatus.Connecting, 5_000),
]);
} catch {
destroySession(interaction.guildId);
}
});
return session;
}
function trackEmbed(title, description, extra = []) {
return new EmbedBuilder()
.setColor(0x9b5cff)
.setTitle(title)
.setDescription(description)
.addFields(extra)
.setFooter({ text: "Jason Discord Host V1 · Music Bot · direct audio URLs only" })
.setTimestamp();
}
async function registerCommands() {
if (GUILD_ID) {
const guild = await client.guilds.fetch(GUILD_ID);
await guild.commands.set(commands);
console.log(`Registered guild commands in ${guild.name}`);
return;
}
await client.application.commands.set(commands);
console.log("Registered global slash commands.");
}
client.once(Events.ClientReady, async () => {
console.log(`Music Bot online as ${client.user.tag}`);
try {
await registerCommands();
} catch (error) {
console.error("Failed to register commands:", error);
}
});
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand()) return;
try {
if (interaction.commandName === "ping") {
await interaction.reply({
content: `Pong — ${client.ws.ping}ms. This bot plays direct audio URLs only (not YouTube/Spotify).`,
flags: MessageFlags.Ephemeral,
});
return;
}
if (!interaction.inGuild()) {
await interaction.reply({ content: "Use music commands in a server.", flags: MessageFlags.Ephemeral });
return;
}
if (interaction.commandName === "play") {
const url = interaction.options.getString("url", true).trim();
if (!isDirectAudioUrl(url)) {
await interaction.reply({
content:
"That is not a supported source. Paste a direct `https://` audio file. YouTube and Spotify are intentionally unsupported — extractors break and often violate platform terms.",
flags: MessageFlags.Ephemeral,
});
return;
}
const session = await ensureSession(interaction);
const title = decodeURIComponent(url.split("/").pop() || "Audio track");
session.queue.push({ url, title, requestedBy: interaction.user.tag });
if (session.queue.length === 1) {
playNext(interaction.guildId);
await interaction.reply({
embeds: [trackEmbed("Now playing", title, [{ name: "Requested by", value: interaction.user.tag, inline: true }])],
});
} else {
await interaction.reply({
embeds: [trackEmbed("Added to queue", title, [{ name: "Position", value: String(session.queue.length), inline: true }])],
});
}
return;
}
const session = getSession(interaction.guildId);
if (!session || session.queue.length === 0) {
await interaction.reply({ content: "Nothing is playing. Use `/play` with a direct audio URL.", flags: MessageFlags.Ephemeral });
return;
}
if (interaction.commandName === "skip") {
const skipped = session.queue[0];
session.player.stop(true);
await interaction.reply({ content: `Skipped **${skipped.title}**.` });
return;
}
if (interaction.commandName === "queue") {
const lines = session.queue.map((track, index) => {
const mark = index === 0 ? "▶️" : `${index}.`;
return `${mark} ${track.title} — ${track.requestedBy}`;
});
await interaction.reply({ embeds: [trackEmbed("Queue", lines.join("\n").slice(0, 4000))] });
return;
}
if (interaction.commandName === "pause") {
session.player.pause();
await interaction.reply({ content: "Paused." });
return;
}
if (interaction.commandName === "resume") {
session.player.unpause();
await interaction.reply({ content: "Resumed." });
return;
}
if (interaction.commandName === "stop") {
destroySession(interaction.guildId);
await interaction.reply({ content: "Stopped playback and left the voice channel." });
return;
}
if (interaction.commandName === "nowplaying") {
await interaction.reply({ embeds: [trackEmbed("Now playing", session.queue[0].title)] });
}
} catch (error) {
console.error(error);
const message = error.message || "Voice connection failed. Is FFmpeg installed?";
if (interaction.deferred || interaction.replied) {
await interaction.followUp({ content: message, flags: MessageFlags.Ephemeral }).catch(() => null);
} else {
await interaction.reply({ content: message, flags: MessageFlags.Ephemeral }).catch(() => null);
}
}
});
client.login(TOKEN).catch((error) => {
console.error("Login failed. Check DISCORD_TOKEN.", error);
process.exit(1);
});