Skip to content
Merged
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
6 changes: 3 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
BOT_TOKEN=your_bot_token_here
CLIENT_ID=your_client_id_here
STATUS_WEBHOOK_URL=your_status_webhook_url_here
BOT_TOKEN=your_discord_bot_token_here
CLIENT_ID=your_discord_client_id_here
STATUS_WEBHOOK_URL=https://discord.com/api/webhooks/your_webhook_id/your_webhook_token
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
node_modules/
.env
data/
data/*.db
data/*.db-wal
data/*.db-shm
MESSAGE_CONTENT_INTENT.md
config.js
13 changes: 9 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
"main": "src/index.js",
"scripts": {
"start": "node src/index.js",
"deploy": "node src/deploy-commands.js"
"deploy": "node src/deploy-commands.js",
"backup": "node scripts/backup-db.js",
"restore-db": "node scripts/restore-db.js"
},
"dependencies": {
"bottleneck": "^2.19.5",
"discord.js": "14.25.1",
"dotenv": "^16.4.7",
"musicard": "latest",
Expand Down
17 changes: 17 additions & 0 deletions scripts/backup-db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
require("dotenv").config();

const db = require("../src/db/sqlite");
const { runBackup } = require("../src/db/backup");

runBackup(db, { force: true })
.then((backupPath) => {
if (!backupPath) {
console.error("[Musicify] No database file found to back up.");
process.exit(1);
}
process.exit(0);
})
.catch((err) => {
console.error("[Musicify] Backup failed:", err.message);
process.exit(1);
});
56 changes: 56 additions & 0 deletions scripts/restore-db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
require("dotenv").config();

const fs = require("fs");
const path = require("path");
const {
BACKUP_DIR,
DB_PATH,
listTimestampedBackups,
} = require("../src/db/backup");

function usage() {
console.log("Usage: node scripts/restore-db.js [backup-file-or-name]");
console.log("");
console.log("Examples:");
console.log(" node scripts/restore-db.js latest");
console.log(" node scripts/restore-db.js musicify-2026-07-07T05-00-00.db");
console.log("");
console.log("Stop the bot before restoring.");
}

const arg = process.argv[2];
if (!arg) {
usage();
process.exit(1);
}

let sourcePath;
if (arg === "latest") {
sourcePath = path.join(BACKUP_DIR, "musicify-latest.db");
} else if (path.isAbsolute(arg)) {
sourcePath = arg;
} else if (arg.includes(path.sep)) {
sourcePath = path.resolve(arg);
} else {
sourcePath = path.join(BACKUP_DIR, arg);
}

if (!fs.existsSync(sourcePath)) {
console.error(`[Musicify] Backup not found: ${sourcePath}`);
console.error("");
console.error("Available backups:");
for (const backup of listTimestampedBackups()) {
console.error(` ${backup.name}`);
}
process.exit(1);
}

const safetyCopy = `${DB_PATH}.before-restore-${Date.now()}`;
if (fs.existsSync(DB_PATH)) {
fs.copyFileSync(DB_PATH, safetyCopy);
console.log(`[Musicify] Saved current database to ${safetyCopy}`);
}

fs.mkdirSync(path.dirname(DB_PATH), { recursive: true });
fs.copyFileSync(sourcePath, DB_PATH);
console.log(`[Musicify] Restored database from ${sourcePath}`);
146 changes: 98 additions & 48 deletions src/commands/247.js
Original file line number Diff line number Diff line change
@@ -1,59 +1,109 @@
const { SlashCommandBuilder, MessageFlags, ContainerBuilder, TextDisplayBuilder } = require("discord.js");
const { getGuildData } = require("../utils/playerStore");
const { setGuildSetting, getGuildSettings } = require("../utils/database");
const {
SlashCommandBuilder,
MessageFlags,
ContainerBuilder,
TextDisplayBuilder,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
} = require("discord.js");
const { getGuildSettings } = require("../utils/database");

module.exports = {
data: new SlashCommandBuilder()
.setName("247")
.setDescription("Toggle 24/7 mode — bot stays in VC even when queue is empty"),
function build247ConfirmContainer(isEnabled, inVoiceChannel = true) {
const container = new ContainerBuilder();
const voiceNote = inVoiceChannel
? ""
: "\n\n-# Join a voice channel to enable or disable 24/7.";

async execute(interaction, client) {
if (!interaction.member.voice?.channel) {
return interaction.reply({
content: "❌ You need to be in a voice channel!",
flags: MessageFlags.Ephemeral,
});
}

const guildId = interaction.guild.id;
const guildData = getGuildData(guildId);
const dbSettings = getGuildSettings(guildId);

const newState = !dbSettings.twentyFourSeven;
guildData.twentyFourSeven = newState;
setGuildSetting(guildId, "twentyFourSeven", newState);

if (newState) {
let player = client.riffy.players.get(guildId);
if (!player) {
player = client.riffy.createConnection({
guildId: guildId,
voiceChannel: interaction.member.voice.channel.id,
textChannel: interaction.channel.id,
deaf: true,
});
}
}

const emoji = newState ? "✅" : "⏹";
const label = newState ? "Enabled" : "Disabled";
const desc = newState
? "Active — I'll stay in the voice channel."
: "Inactive — I'll leave when the queue is empty.";

const container = new ContainerBuilder();
if (isEnabled) {
container.addTextDisplayComponents(
new TextDisplayBuilder().setContent(
`### ${emoji} 24/7 Mode ${label}\n\n` +
"**Status**\n" +
`-# ${desc}\n\n` +
"**Persists**\n" +
"-# This setting is saved across bot restarts."
"### 24/7 Mode\n\n" +
"**Current status**\n" +
"-# Enabled — I stay in voice even when the queue is empty.\n\n" +
"**Disable 24/7?**\n" +
"-# I'll leave the voice channel once playback stops and the queue is empty." +
voiceNote
)
);

container.addActionRowComponents(
new ActionRowBuilder().addComponents(
new ButtonBuilder()
.setCustomId("247_disable")
.setLabel("Disable 24/7")
.setStyle(ButtonStyle.Danger),
new ButtonBuilder()
.setCustomId("247_cancel")
.setLabel("Cancel")
.setStyle(ButtonStyle.Secondary)
)
);
} else {
container.addTextDisplayComponents(
new TextDisplayBuilder().setContent(
"### 24/7 Mode\n\n" +
"**Current status**\n" +
"-# Inactive — I leave when the queue is empty.\n\n" +
"**Enable 24/7?**\n" +
"-# I'll stay connected to your voice channel even between songs." +
voiceNote
)
);

container.addActionRowComponents(
new ActionRowBuilder().addComponents(
new ButtonBuilder()
.setCustomId("247_enable")
.setLabel("Enable 24/7")
.setStyle(ButtonStyle.Success),
new ButtonBuilder()
.setCustomId("247_cancel")
.setLabel("Cancel")
.setStyle(ButtonStyle.Secondary)
)
);
}

return container;
}

function build247ResultContainer(isEnabled) {
const container = new ContainerBuilder();
container.addTextDisplayComponents(
new TextDisplayBuilder().setContent(
isEnabled
? "### ✅ 24/7 Enabled\n\n**Status**\n-# I'll stay in the voice channel even when nothing is playing."
: "### ⏹ 24/7 Disabled\n\n**Status**\n-# I'll leave the voice channel when the queue is empty."
)
);
return container;
}

function build247CancelledContainer() {
const container = new ContainerBuilder();
container.addTextDisplayComponents(
new TextDisplayBuilder().setContent("### Cancelled\n\n-# 24/7 mode was not changed.")
);
return container;
}

module.exports = {
data: new SlashCommandBuilder()
.setName("247")
.setDescription("Toggle 24/7 mode — bot stays in VC even when queue is empty"),

async execute(interaction) {
const isEnabled = Boolean(getGuildSettings(interaction.guild.id).twentyFourSeven);
const inVoiceChannel = Boolean(interaction.member.voice?.channel);

await interaction.reply({
components: [container],
components: [build247ConfirmContainer(isEnabled, inVoiceChannel)],
flags: MessageFlags.Ephemeral | MessageFlags.IsComponentsV2,
});
},

build247ConfirmContainer,
build247ResultContainer,
build247CancelledContainer,
};
2 changes: 1 addition & 1 deletion src/commands/about.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ module.exports = {
new SectionBuilder()
.addTextDisplayComponents(
new TextDisplayBuilder().setContent(
"# <:Musicify_Logo:1504329028356673536> About Musicify"
"# <:Musicify_Logo:1517828581638541493> About Musicify"
)
)
.setThumbnailAccessory(
Expand Down
2 changes: 1 addition & 1 deletion src/commands/bot-stats.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ module.exports = {

const header = new SectionBuilder()
.addTextDisplayComponents(
new TextDisplayBuilder().setContent("### <:Musicify_Logo:1504329028356673536> Statistics")
new TextDisplayBuilder().setContent("### <:Musicify_Logo:1517828581638541493> Statistics")
)
.setThumbnailAccessory(
new ThumbnailBuilder().setURL(
Expand Down
Loading
Loading