first commit
This commit is contained in:
49
.eslintrc.json
Normal file
49
.eslintrc.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"extends": "eslint:recommended",
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2021
|
||||
},
|
||||
"rules": {
|
||||
"arrow-spacing": ["warn", { "before": true, "after": true }],
|
||||
"brace-style": ["error", "stroustrup", { "allowSingleLine": true }],
|
||||
"comma-dangle": ["error", "always-multiline"],
|
||||
"comma-spacing": "error",
|
||||
"comma-style": "error",
|
||||
"curly": ["error", "multi-line", "consistent"],
|
||||
"dot-location": ["error", "property"],
|
||||
"handle-callback-err": "off",
|
||||
"indent": ["error", "tab"],
|
||||
"keyword-spacing": "error",
|
||||
"max-nested-callbacks": ["error", { "max": 4 }],
|
||||
"max-statements-per-line": ["error", { "max": 2 }],
|
||||
"no-console": "off",
|
||||
"no-empty-function": "error",
|
||||
"no-floating-decimal": "error",
|
||||
"no-inline-comments": "error",
|
||||
"no-lonely-if": "error",
|
||||
"no-multi-spaces": "error",
|
||||
"no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF": 0 }],
|
||||
"no-shadow": ["error", { "allow": ["err", "resolve", "reject"] }],
|
||||
"no-trailing-spaces": ["error"],
|
||||
"no-var": "error",
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
"prefer-const": "error",
|
||||
"quotes": ["error", "single"],
|
||||
"semi": ["error", "always"],
|
||||
"space-before-blocks": "error",
|
||||
"space-before-function-paren": ["error", {
|
||||
"anonymous": "never",
|
||||
"named": "never",
|
||||
"asyncArrow": "always"
|
||||
}],
|
||||
"space-in-parens": "error",
|
||||
"space-infix-ops": "error",
|
||||
"space-unary-ops": "error",
|
||||
"spaced-comment": "error",
|
||||
"yoda": "error"
|
||||
}
|
||||
}
|
||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
.env
|
||||
config.json
|
||||
dockerfile
|
||||
92
commands/misc/pinguser.js
Normal file
92
commands/misc/pinguser.js
Normal file
@ -0,0 +1,92 @@
|
||||
const { Client, Interaction, SlashCommandBuilder } = require("discord.js");
|
||||
|
||||
const cooldowns = new Map();
|
||||
var isDisabled = false;
|
||||
|
||||
/**
|
||||
* Handles the ping user command.
|
||||
* @param {Interaction} interaction - The interaction object.
|
||||
*/
|
||||
async function handlePingCommand(interaction) {
|
||||
const user = interaction.options.getUser("user"); // Get the user from the interaction
|
||||
const times = interaction.options.getInteger("times") || 1; // Default to 1 if not provided
|
||||
|
||||
try {
|
||||
// Ensure the user exists in the guild
|
||||
const userPing = await interaction.guild.members.fetch(user.id);
|
||||
if (!userPing) {
|
||||
await interaction.reply("That user doesn't exist in this server.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent the bot from pinging itself
|
||||
if (userPing.id === interaction.guild.members.me.id) {
|
||||
await interaction.reply("I can't ping myself.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Cooldown logic
|
||||
const coolDown = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
|
||||
const now = Date.now();
|
||||
const lastExec = cooldowns.get(interaction.user.id);
|
||||
|
||||
if (lastExec && now - lastExec < coolDown) {
|
||||
const timeLeft = coolDown - (now - lastExec);
|
||||
|
||||
const days = Math.floor(timeLeft / (1000 * 60 * 60 * 24));
|
||||
const hours = Math.floor((timeLeft % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
||||
const minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60));
|
||||
const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000);
|
||||
|
||||
await interaction.reply(
|
||||
`You need to wait ${days} days, ${hours} hours, ${minutes} minutes, and ${seconds} seconds before using this command again.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Send the pings
|
||||
await interaction.reply({ content: "Ok, sending pings.", ephemeral: false });
|
||||
|
||||
for (let i = 0; i < times; i++) {
|
||||
await interaction.channel.send(`Pinging ${userPing}`);
|
||||
await new Promise((resolve) => setTimeout(resolve, 300)); // Wait 300ms between pings
|
||||
}
|
||||
|
||||
// Update the cooldown
|
||||
cooldowns.set(interaction.user.id, now);
|
||||
} catch (error) {
|
||||
console.error("There was an error in pinguser: ", error);
|
||||
await interaction.reply({
|
||||
content: "An error occurred while executing the command.",
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("pinguser")
|
||||
.setDescription("Ping the mentioned user")
|
||||
.addUserOption((option) =>
|
||||
option.setName("user").setDescription("The user to ping").setRequired(true),
|
||||
)
|
||||
.addIntegerOption((option) =>
|
||||
option
|
||||
.setName("times")
|
||||
.setDescription("Number of times to ping the user")
|
||||
.setRequired(false),
|
||||
),
|
||||
async execute(interaction) {
|
||||
try {
|
||||
if (isDisabled) {
|
||||
return interaction.reply({
|
||||
content: "The command is disabled... Try again later.",
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
await handlePingCommand(interaction); // Ensure we await this function
|
||||
} catch (error) {
|
||||
console.error("There was an error executing the command: ", error);
|
||||
}
|
||||
},
|
||||
};
|
||||
92
commands/moderation/ban.js
Normal file
92
commands/moderation/ban.js
Normal file
@ -0,0 +1,92 @@
|
||||
const {
|
||||
Client,
|
||||
Interaction,
|
||||
ApplicationCommandOptionType,
|
||||
PermissionFlagsBits,
|
||||
SlashCommandBuilder,
|
||||
} = require("discord.js");
|
||||
|
||||
async function handleBanCommand(interaction) {
|
||||
if (!interaction.member.permissions.has(PermissionFlagsBits.BanMembers)) {
|
||||
await interaction.reply({
|
||||
content: "You do not have permission to ban members.",
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const targetID = interaction.options.get("user").value;
|
||||
const reason =
|
||||
interaction.options.get("reason")?.value || "No reason provided";
|
||||
|
||||
await interaction.deferReply();
|
||||
const targetUser = await interaction.guild.members.fetch(targetID);
|
||||
|
||||
if (!targetUser) {
|
||||
await interaction.editReply("That user is not in the server");
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetUser.id === interaction.guild.ownerId) {
|
||||
await interaction.editReply("I can't ban the server owner");
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetUser.id === interaction.guild.members.me) {
|
||||
await interaction.editReply("I cant't ban myself");
|
||||
return;
|
||||
}
|
||||
|
||||
const targetUserRolePostion = targetUser.roles.highest.postion; // check the user highest role
|
||||
const requestUserRolePostion = interaction.member.roles.highest.postion; // check the user issuing the command is higher than the target
|
||||
const botRolePostion = interaction.guild.members.me.roles.highest.postion; // check the bot has permissions
|
||||
|
||||
if (targetUserRolePostion >= requestUserRolePostion) {
|
||||
await interaction.editReply(
|
||||
"You can't ban someone that has the same/higher role than you."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetUserRolePostion >= botRolePostion) {
|
||||
await interaction.editReply(
|
||||
"I can't ban that user because they have the same/higher role than me."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await targetUser.ban({ reason });
|
||||
await interaction.editReply(
|
||||
`User ${targetUser} was banned. Reason: ${reason}`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("There was an error banning the target: ", error);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("ban")
|
||||
.setDescription("Bans the specified user")
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName("user")
|
||||
.setDescription("Mention the user to ban")
|
||||
.setRequired(true)
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("reason")
|
||||
.setDescription("Specify the reason for banning")
|
||||
.setRequired(false)
|
||||
)
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.BanMembers),
|
||||
async execute(interaction) {
|
||||
try {
|
||||
handleBanCommand(interaction);
|
||||
} catch (error) {
|
||||
console.error("There was an error in banning: ", error);
|
||||
}
|
||||
},
|
||||
};
|
||||
75
commands/moderation/kick.js
Normal file
75
commands/moderation/kick.js
Normal file
@ -0,0 +1,75 @@
|
||||
const {
|
||||
Client,
|
||||
Interaction,
|
||||
ApplicationCommandOptionType,
|
||||
PermissionFlagsBits,
|
||||
SlashCommandBuilder,
|
||||
} = require("discord.js");
|
||||
|
||||
async function handleKickCommand(interaction) {
|
||||
if (!interaction.member.permissions.has(PermissionFlagsBits.KickMembers)) {
|
||||
await interaction.reply({
|
||||
content: "You do not have permission to kick members.",
|
||||
ephemeral: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const targetID = interaction.options.get('user').value;
|
||||
const reason = interaction.options.get("reason")?.value || "No reason provided";
|
||||
|
||||
await interaction.deferReply();
|
||||
const targetUser = await interaction.guild.members.fetch(targetID)
|
||||
|
||||
if (!targetUser) {
|
||||
await interaction.editReply('That user does not exist in this server');
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetUser.id === interaction.guild.ownerId) {
|
||||
await interaction.editReply("I can't kick the server owner")
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetUser.id === interaction.guild.members.me) {
|
||||
await interaction.editReply("I can't kick myself")
|
||||
return;
|
||||
}
|
||||
|
||||
const targetUserRolePostion = targetUser.roles.highest.postion;
|
||||
const requestUserRolePostion = interaction.member.roles.highest.postion
|
||||
const botRolePostion = interaction.guild.members.me.roles.highest.postion
|
||||
|
||||
if (targetUserRolePostion >= requestUserRolePostion) {
|
||||
await interaction.editReply("You can't kick someone that has the same/higher role than you")
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetUserRolePostion >= botRolePostion) {
|
||||
await interaction.editReply("I can't kick that user because they have the same/higher role than me.")
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await targetUser.kick({ reason })
|
||||
await interaction.editReply(`User ${targetUser} was kicked. Reason: ${reason}`)
|
||||
} catch (error) {
|
||||
console.error('There was a problem kicking: ', error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("kick")
|
||||
.setDescription("Kicks the specified user")
|
||||
.addUserOption((option) => option.setName("user").setDescription("Mention the user to kick").setRequired(true))
|
||||
.addStringOption((option) => option.setName('reason').setDescription('Specify the reason for kicking').setRequired(false))
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.KickMembers),
|
||||
async execute(interaction) {
|
||||
try {
|
||||
handleKickCommand(interaction)
|
||||
} catch (error) {
|
||||
console.error('There was an error in kicking:', error)
|
||||
}
|
||||
}
|
||||
};
|
||||
132
commands/music/bplay.js
Normal file
132
commands/music/bplay.js
Normal file
@ -0,0 +1,132 @@
|
||||
const { Client, Interaction, SlashCommandBuilder, VoiceChannel } = require('discord.js');
|
||||
const { playFromQueue, queue} = require('../../playback');
|
||||
const ytdl = require('ytdl-core');
|
||||
const { default: axios } = require('axios');
|
||||
const { setStopped, cancelTimer } = require('../../playback')
|
||||
var isDisabled = false
|
||||
|
||||
const API_KEY = process.env.YoutubeToken
|
||||
/**
|
||||
*
|
||||
* @param {Client} client
|
||||
* @param {Interaction} interaction
|
||||
*/
|
||||
|
||||
|
||||
async function addToQueue(interaction, link, songName) {
|
||||
const voiceChannel = interaction.member.voice.channel;
|
||||
const textChannel = interaction.channel;
|
||||
const getMember = interaction.member.displayName;
|
||||
|
||||
const wasQueueEmpty = queue.length === 0;
|
||||
queue.push({interaction, getMember, link, songName})
|
||||
cancelTimer()
|
||||
if (wasQueueEmpty) {
|
||||
await playFromQueue(voiceChannel, textChannel, getMember, link, songName)
|
||||
} else {
|
||||
//console.log(queue)
|
||||
interaction.reply(`Added to queue: ${songName}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function searchYoutubeSong(input) {
|
||||
try {
|
||||
const response = await axios.get('https://www.googleapis.com/youtube/v3/search', {
|
||||
params: {
|
||||
part: 'snippet',
|
||||
q: input,
|
||||
key: API_KEY,
|
||||
maxResults: 1,
|
||||
type: 'video',
|
||||
}
|
||||
});
|
||||
|
||||
if (response.data.items && response.data.items.length > 0) {
|
||||
const video = response.data.items[0];
|
||||
const videoId = video.id.videoId;
|
||||
const videoTitle = video.snippet.title;
|
||||
const videoUrl = `https://www.youtube.com/watch?v=${videoId}`;
|
||||
return { videoUrl, videoTitle}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error searching Youtube: ', error)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function handlePlayCommand(interaction, input) {
|
||||
const voiceChannel = interaction.member.voice.channel;
|
||||
|
||||
if (!voiceChannel) {
|
||||
return interaction.reply({
|
||||
content: 'You need to be in a voice channel to use this command.',
|
||||
ephemeral: true
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
let link = '';
|
||||
let songName = input;
|
||||
|
||||
if (ytdl.validateURL(input)) {
|
||||
// If it's a valid YouTube URL, extract the song information
|
||||
const videoInfo = await ytdl.getBasicInfo(input);
|
||||
songName = videoInfo.videoDetails.title;
|
||||
link = input;
|
||||
} else {
|
||||
// If it's a search query, find the video
|
||||
const searchResult = await searchYoutubeSong(input);
|
||||
if (searchResult) {
|
||||
link = searchResult.videoUrl;
|
||||
songName = searchResult.videoTitle;
|
||||
} else {
|
||||
return interaction.reply({
|
||||
content: 'No results found for your query.',
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Add the song to the queue and play if needed
|
||||
await addToQueue(interaction, link, songName);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error handling play command:', error);
|
||||
await interaction.reply({
|
||||
content: 'An error occurred while processing your request.',
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('bplay')
|
||||
.setDescription('Plays a song')
|
||||
.addStringOption(option =>
|
||||
option.setName('query')
|
||||
.setDescription('The song name or URL to play')
|
||||
.setRequired(true)
|
||||
),
|
||||
|
||||
async execute(interaction) {
|
||||
try {
|
||||
if (isDisabled) return interaction.reply({ content: 'The command is Disabled... Try again later', ephemeral: true})
|
||||
const query = interaction.options.getString('query');
|
||||
setStopped(false)
|
||||
await handlePlayCommand(interaction, query);
|
||||
} catch (error) {
|
||||
console.error('Error executing play command:', error);
|
||||
await interaction.reply({
|
||||
content: 'An error occurred while processing your request.',
|
||||
ephemeral: true
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
36
commands/music/queue.js
Normal file
36
commands/music/queue.js
Normal file
@ -0,0 +1,36 @@
|
||||
const { Client, Interaction, SlashCommandBuilder, EmbedBuilder } = require('discord.js')
|
||||
const { queue } = require('./../../playback')
|
||||
const { musicQueueEmbed } = require('../../embeds/queue');
|
||||
|
||||
async function checkQueue(interaction) {
|
||||
if (!queue || queue.length === 0 ) {
|
||||
interaction.reply('The queue is empty.')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const embed = musicQueueEmbed(queue);
|
||||
await interaction.reply({ embeds: [embed] })
|
||||
} catch (error) {
|
||||
console.log('There is an error in the queue command: ', error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('queue')
|
||||
.setDescription('Check the playback queue'),
|
||||
async execute(interaction) {
|
||||
const voiceChannel = interaction.member.voice.channel
|
||||
const textChannel = interaction.textChannel
|
||||
|
||||
if (!voiceChannel) {
|
||||
return interaction.reply({
|
||||
content: 'You need to be in a voice channel to use that queue',
|
||||
ephemeral: true
|
||||
})
|
||||
}
|
||||
|
||||
await checkQueue(interaction, voiceChannel, textChannel)
|
||||
}
|
||||
}
|
||||
60
commands/music/skip.js
Normal file
60
commands/music/skip.js
Normal file
@ -0,0 +1,60 @@
|
||||
const { Client, Interaction, SlashCommandBuilder } = require('discord.js')
|
||||
|
||||
const { playFromQueue, queue } = require('../../playback');
|
||||
const { getVoiceConnection } = require('@discordjs/voice')
|
||||
|
||||
async function skipSong(voiceChannel, textChannel) {
|
||||
const connection = getVoiceConnection(voiceChannel.guild.id)
|
||||
|
||||
if (!connection) {
|
||||
console.log('No active voice connections')
|
||||
textChannel.send('No active voice connections')
|
||||
return
|
||||
}
|
||||
|
||||
if (!queue || queue.length === 0) {
|
||||
console.log('Queue is empty. No songs to skip')
|
||||
connection.state.subscription.player.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
queue.shift()
|
||||
|
||||
if (queue.length > 0 ) {
|
||||
const nextSong = queue[0];
|
||||
await playFromQueue(voiceChannel, textChannel, nextSong.getMember, nextSong.link, nextSong.songName)
|
||||
} else {
|
||||
textChannel.send('No more songs in the queue')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error skipping song: ', error)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('skip')
|
||||
.setDescription('Skips a song in the queue'),
|
||||
async execute(interaction) {
|
||||
const voiceChannel = interaction.member.voice.channel
|
||||
const textChannel = interaction.channel
|
||||
if (!voiceChannel) {
|
||||
return interaction.reply({
|
||||
content: 'You need to be in a voice channel to use the command',
|
||||
ephemeral: true
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await skipSong(voiceChannel, textChannel)
|
||||
interaction.reply('Skipped the current song')
|
||||
} catch (error) {
|
||||
console.error('Error in skip command: ', error)
|
||||
interaction.reply({
|
||||
content: 'An error has occurred while processing the command',
|
||||
ephemeral: true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
20
commands/music/stop.js
Normal file
20
commands/music/stop.js
Normal file
@ -0,0 +1,20 @@
|
||||
const { SlashCommandBuilder } = require("discord.js");
|
||||
const { resetAll, setStopped } = require("../../playback");
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("stop")
|
||||
.setDescription("Stop playback and leave the voice channel"),
|
||||
|
||||
async execute(interaction) {
|
||||
const voiceChannel = interaction.member.voice.channel;
|
||||
if (!voiceChannel) {
|
||||
return interaction.reply({ content: "You need to be in a voice channel to use this command.", ephemeral: true });
|
||||
}
|
||||
|
||||
setStopped(true); // Mark playback as stopped
|
||||
resetAll(voiceChannel.guild.id); // Reset everything for a clean state
|
||||
|
||||
return interaction.reply("Stopped playback and left the voice channel.");
|
||||
},
|
||||
};
|
||||
46
commands/utility/auto-welcome.js
Normal file
46
commands/utility/auto-welcome.js
Normal file
@ -0,0 +1,46 @@
|
||||
const {
|
||||
Client,
|
||||
Interaction,
|
||||
ApplicationCommandOptionType,
|
||||
PermissionFlagsBits,
|
||||
SlashCommandBuilder,
|
||||
} = require("discord.js");
|
||||
const db = require("../../db");
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("autowelcome")
|
||||
.setDescription("Enable or disable auto-welcome messages.")
|
||||
.addChannelOption((option) =>
|
||||
option
|
||||
.setName("channel")
|
||||
.setDescription(
|
||||
"Channel Id to send welcome messages (required when enabling)"
|
||||
)
|
||||
.setRequired(false)
|
||||
)
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
||||
|
||||
async execute(interaction) {
|
||||
const channelId = interaction.options.getChannel("channel");
|
||||
const guildId = interaction.guild.id;
|
||||
|
||||
if (channelId) {
|
||||
await db.query(
|
||||
`INSERT INTO auto_welcome (guild_id, channel_id, enabled)
|
||||
VALUES ($1, $2, $3)
|
||||
ON CONFLICT (guild_id) DO UPDATE SET channel_id = $2, enabled = $3`,
|
||||
[guildId, channelId.id, true]
|
||||
);
|
||||
return interaction.reply(
|
||||
`Auto-welcome enabled! Messages will be sent to <#${channelId.id}>.`
|
||||
);
|
||||
} else {
|
||||
await db.query(
|
||||
`UPDATE auto_welcome SET enabled = $1 WHERE guild_id = $2`,
|
||||
[false, guildId]
|
||||
);
|
||||
return interaction.reply("Auto-welcome has been disabled.");
|
||||
}
|
||||
},
|
||||
};
|
||||
17
commands/utility/ping.js
Normal file
17
commands/utility/ping.js
Normal file
@ -0,0 +1,17 @@
|
||||
const { SlashCommandBuilder } = require("discord.js");
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("ping")
|
||||
.setDescription("Replies with Pong! and websocket ping"),
|
||||
async execute(interaction) {
|
||||
// Send the initial reply and wait for it to be sent
|
||||
const reply = await interaction.reply({
|
||||
content: "Pong!",
|
||||
fetchReply: true // This ensures we can get the reply object
|
||||
});
|
||||
|
||||
const ping = reply.createdTimestamp - interaction.createdTimestamp; // Calculate the ping
|
||||
await interaction.editReply(`Pong! client ${ping}ms | websocket: ${interaction.client.ws.ping}ms`);
|
||||
},
|
||||
};
|
||||
33
commands/utility/setBluChannel.js
Normal file
33
commands/utility/setBluChannel.js
Normal file
@ -0,0 +1,33 @@
|
||||
const { Client, Interaction, ApplicationCommandOptionType, PermissionFlagsBits, SlashCommandBuilder } = require("discord.js");
|
||||
const db = require("../../db");
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("setbluchannel")
|
||||
.setDescription("Set the channel for Blu to send messages.")
|
||||
.addChannelOption((option) =>
|
||||
option
|
||||
.setName("channel")
|
||||
.setDescription("Channel to set for Blu messages")
|
||||
.setRequired(true)
|
||||
)
|
||||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
||||
async execute(interaction) {
|
||||
const channelId = interaction.options.getChannel("channel");
|
||||
const guildId = interaction.guild.id;
|
||||
const guildName = interaction.guild.name;
|
||||
const channelName = channelId.name;
|
||||
|
||||
if (channelId) {
|
||||
await db.query(
|
||||
`INSERT INTO bot_channel (guild_id, channel_id, guild_name, channel_name)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
ON CONFLICT (guild_id) DO UPDATE SET Channel_id = $2, guild_name = $3, channel_name = $4`,
|
||||
[guildId, channelId.id, guildName, channelName]
|
||||
);
|
||||
return interaction.reply(`Blu messages will be sent to <#${channelId.id}>.`);
|
||||
} else {
|
||||
return interaction.reply("Please provide a valid channel.");
|
||||
}
|
||||
},
|
||||
}
|
||||
37
db.js
Normal file
37
db.js
Normal file
@ -0,0 +1,37 @@
|
||||
const { Pool } = require('pg');
|
||||
require('dotenv').config();
|
||||
|
||||
const pool = new Pool({
|
||||
host: process.env.PGHOST,
|
||||
user: process.env.PGUSER,
|
||||
database: process.env.PGDATABASE,
|
||||
password: process.env.PGPASSWORD,
|
||||
port: process.env.PGPORT,
|
||||
});
|
||||
|
||||
async function connectDB() {
|
||||
try {
|
||||
await pool.connect();
|
||||
console.log('Database connected.')
|
||||
} catch (error) {
|
||||
console.error('Failed to connect to the database: ',error)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
function closeDB() {
|
||||
try {
|
||||
pool.end();
|
||||
console.log('Database connection closed.')
|
||||
} catch (error) {
|
||||
console.error('Error closing the database: ', error )
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
query: (text, params) => pool.query(text, params),
|
||||
closeDB,
|
||||
connectDB
|
||||
};
|
||||
46
deploy-commands-private.js
Normal file
46
deploy-commands-private.js
Normal file
@ -0,0 +1,46 @@
|
||||
const { REST, Routes } = require('discord.js');
|
||||
require('dotenv').config();
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const commands = [];
|
||||
// Grab all the command folders from the commands directory you created earlier
|
||||
const foldersPath = path.join(__dirname, 'commands');
|
||||
const commandFolders = fs.readdirSync(foldersPath);
|
||||
|
||||
for (const folder of commandFolders) {
|
||||
// Grab all the command files from the commands directory you created earlier
|
||||
const commandsPath = path.join(foldersPath, folder);
|
||||
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
|
||||
// Grab the SlashCommandBuilder#toJSON() output of each command's data for deployment
|
||||
for (const file of commandFiles) {
|
||||
const filePath = path.join(commandsPath, file);
|
||||
const command = require(filePath);
|
||||
if ('data' in command && 'execute' in command) {
|
||||
commands.push(command.data.toJSON());
|
||||
} else {
|
||||
console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Construct and prepare an instance of the REST module
|
||||
const rest = new REST().setToken(process.env.TOKEN);
|
||||
|
||||
// and deploy your commands!
|
||||
(async () => {
|
||||
try {
|
||||
console.log(`Started refreshing ${commands.length} application (/) commands.`);
|
||||
|
||||
// The put method is used to fully refresh all commands in the guild with the current set
|
||||
const data = await rest.put(
|
||||
Routes.applicationGuildCommands(process.env.ClientID, process.env.GuildID),
|
||||
{ body: commands },
|
||||
);
|
||||
|
||||
console.log(`Successfully reloaded ${data.length} application (/) commands.`);
|
||||
} catch (error) {
|
||||
// And of course, make sure you catch and log any errors!
|
||||
console.error(error);
|
||||
}
|
||||
})();
|
||||
46
deploy-commands-public.js
Normal file
46
deploy-commands-public.js
Normal file
@ -0,0 +1,46 @@
|
||||
const { REST, Routes } = require('discord.js');
|
||||
require('dotenv').config();
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const commands = [];
|
||||
// Grab all the command folders from the commands directory you created earlier
|
||||
const foldersPath = path.join(__dirname, 'commands');
|
||||
const commandFolders = fs.readdirSync(foldersPath);
|
||||
|
||||
for (const folder of commandFolders) {
|
||||
// Grab all the command files from the commands directory you created earlier
|
||||
const commandsPath = path.join(foldersPath, folder);
|
||||
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
|
||||
// Grab the SlashCommandBuilder#toJSON() output of each command's data for deployment
|
||||
for (const file of commandFiles) {
|
||||
const filePath = path.join(commandsPath, file);
|
||||
const command = require(filePath);
|
||||
if ('data' in command && 'execute' in command) {
|
||||
commands.push(command.data.toJSON());
|
||||
} else {
|
||||
console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Construct and prepare an instance of the REST module
|
||||
const rest = new REST().setToken(process.env.TOKEN);
|
||||
|
||||
// and deploy your commands!
|
||||
(async () => {
|
||||
try {
|
||||
console.log(`Started refreshing ${commands.length} application (/) commands.`);
|
||||
|
||||
// The put method is used to fully refresh all commands in the guild with the current set
|
||||
const data = await rest.put(
|
||||
Routes.applicationCommands(process.env.ClientID),
|
||||
{ body: commands },
|
||||
);
|
||||
|
||||
console.log(`Successfully reloaded ${data.length} application (/) commands.`);
|
||||
} catch (error) {
|
||||
// And of course, make sure you catch and log any errors!
|
||||
console.error(error);
|
||||
}
|
||||
})();
|
||||
44
embeds/modLogs.js
Normal file
44
embeds/modLogs.js
Normal file
@ -0,0 +1,44 @@
|
||||
const { EmbedBuilder } = require('discord.js')
|
||||
|
||||
function modLogEmbed(action, member, reason ) {
|
||||
let title, description, color;
|
||||
|
||||
//console.log(reason);
|
||||
switch (action) {
|
||||
case "ban":
|
||||
title = ":hammer: User Banned";
|
||||
description = `**User:** **${member.user.username}** has been banned.`;
|
||||
reason2 = `**Reason:** ${reason}`;
|
||||
color = "#FF0000";
|
||||
break;
|
||||
case "unban":
|
||||
title = ":tada::party_popper: User Unbanned";
|
||||
description = `**User:** **${member.user.username}** has been unbanned.`;
|
||||
reason2 = `**Reason:** ${reason}`;
|
||||
color = "#00FF00";
|
||||
break;
|
||||
case "mute":
|
||||
return muteEmbed(member);
|
||||
case "unmute":
|
||||
return unmuteEmbed(member);
|
||||
case "warn":
|
||||
return warnEmbed(member);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return new EmbedBuilder()
|
||||
.setTitle(title)
|
||||
.setDescription(`${description}\n${reason2}`)
|
||||
.setColor(color)
|
||||
.setTimestamp()
|
||||
.setFooter({ text: `User ID: ${member.user.id}` })
|
||||
.setThumbnail(member.user.displayAvatarURL({ dynamic: true }));
|
||||
|
||||
}
|
||||
|
||||
|
||||
module.exports = { modLogEmbed };
|
||||
19
embeds/nowPlaying.js
Normal file
19
embeds/nowPlaying.js
Normal file
@ -0,0 +1,19 @@
|
||||
const { EmbedBuilder } = require('discord.js')
|
||||
|
||||
function nowPlayingEmbed(queue) {
|
||||
if (!queue || queue.length === 0) {
|
||||
return new EmbedBuilder()
|
||||
.setTitle('**🎵 Now Playing:**')
|
||||
.setDescription('No song is currently playing.')
|
||||
.setColor('Grey');
|
||||
}
|
||||
|
||||
const currentSong = queue[0]
|
||||
|
||||
return new EmbedBuilder()
|
||||
.setTitle('**🎵 Now Playing:**')
|
||||
.setDescription(`${currentSong.songName}\n**Requested By:** ${currentSong.getMember || 'Unknown User'}`)
|
||||
.setColor('Green')
|
||||
}
|
||||
|
||||
module.exports = { nowPlayingEmbed }
|
||||
15
embeds/queue.js
Normal file
15
embeds/queue.js
Normal file
@ -0,0 +1,15 @@
|
||||
const { EmbedBuilder } = require('discord.js')
|
||||
|
||||
|
||||
function musicQueueEmbed(queue) {
|
||||
const songLines = queue.map((item, index) =>
|
||||
index === 0 ? `**🎵 Now Playing:** ${item.songName} **Requested By:** ${item.getMember}\n` : `${index + 1}. ${item.songName} - **Requested By:** ${item.getMember}\n`
|
||||
);
|
||||
|
||||
return new EmbedBuilder()
|
||||
.setTitle('Current Queue')
|
||||
.setDescription(songLines.join('\n') || 'No Songs in the queue')
|
||||
.setColor('Blue')
|
||||
}
|
||||
|
||||
module.exports = { musicQueueEmbed }
|
||||
33
embeds/userStreaming.js
Normal file
33
embeds/userStreaming.js
Normal file
@ -0,0 +1,33 @@
|
||||
const { EmbedBuilder, ButtonBuilder, ActionRowBuilder } = require('discord.js');
|
||||
|
||||
function userStreamingEmbed(user, status) {
|
||||
|
||||
const username = user?.username || 'Unknown User';
|
||||
const avatarURL = user?.displayAvatarURL({ dynamic: true }) || null;
|
||||
const streamTitle = status?.details || 'No title available';
|
||||
const gameName = status?.name || 'No game available';
|
||||
const streamURL = status?.url || 'No URL available';
|
||||
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle('📡 Live Stream Alert!')
|
||||
.setDescription(`**${username}** Just went Live on Twitch!`)
|
||||
.addFields( { name: 'Stream Title', value: streamTitle, inline: false }, { name: 'Game / Category', value: gameName, inline: true } )
|
||||
.setColor('Green')
|
||||
.setTimestamp()
|
||||
.setFooter({ text: 'User Streaming Status' })
|
||||
.setThumbnail(avatarURL);
|
||||
|
||||
const streamButton = new ButtonBuilder()
|
||||
.setLabel('🎥 Watch Stream')
|
||||
.setURL(streamURL)
|
||||
.setStyle(5);
|
||||
|
||||
const row = new ActionRowBuilder()
|
||||
.addComponents(streamButton);
|
||||
return { embeds: [embed], components: [row] };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
userStreamingEmbed
|
||||
};
|
||||
32
embeds/welcomeMember.js
Normal file
32
embeds/welcomeMember.js
Normal file
@ -0,0 +1,32 @@
|
||||
const { GuildMember, EmbedBuilder } = require("discord.js");
|
||||
|
||||
/**
|
||||
* @param {Client} client
|
||||
* @param {GuildMember} member
|
||||
*/
|
||||
|
||||
function welcomeEmbed(member) {
|
||||
let welcomeMessages = [
|
||||
`Welcome to the server ${member.user.username}. We hope you enjoy your stay.`,
|
||||
`We hope you brought pizza ${member.user.username}.`,
|
||||
`Yippe.... Welcome to the server ${member.user.username}.`,
|
||||
`A wild ${member.user.username} has appeared.`,
|
||||
];
|
||||
const randomMessage =
|
||||
welcomeMessages[Math.floor(Math.random() * welcomeMessages.length)];
|
||||
|
||||
return new EmbedBuilder()
|
||||
.setTitle("**New Member**")
|
||||
.setDescription(randomMessage)
|
||||
.addFields(
|
||||
{ name: "Joined at", value: `${member.joinedAt}`, inline: true },
|
||||
{
|
||||
name: "User Created at",
|
||||
value: `${member.user.createdAt}`,
|
||||
inline: true,
|
||||
}
|
||||
)
|
||||
.setThumbnail(member.user.avatarURL());
|
||||
}
|
||||
|
||||
module.exports = { welcomeEmbed };
|
||||
26
events/InteractionCreate/interactionCreate.js
Normal file
26
events/InteractionCreate/interactionCreate.js
Normal file
@ -0,0 +1,26 @@
|
||||
const { Events } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
name: Events.InteractionCreate,
|
||||
async execute(interaction) {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
const command = interaction.client.commands.get(interaction.commandName);
|
||||
|
||||
if (!command) {
|
||||
console.error(`No command matching ${interaction.commandName} was found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await command.execute(interaction);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
if (interaction.replied || interaction.deferred) {
|
||||
await interaction.followUp({ content: 'There was an error while executing this command!', ephemeral: true });
|
||||
} else {
|
||||
await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
33
events/guildBanAdd/chatlog.js
Normal file
33
events/guildBanAdd/chatlog.js
Normal file
@ -0,0 +1,33 @@
|
||||
const { Events, GuildMember } = require("discord.js");
|
||||
const { modLogEmbed } = require("../../embeds/modLogs");
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
name: Events.GuildBanAdd,
|
||||
async execute(member) {
|
||||
try {
|
||||
const auditLogs = await member.guild.fetchAuditLogs({ type: 22 });
|
||||
const banEntry = auditLogs.entries.first();
|
||||
const modLogsChannel = member.guild.channels.cache.find(channel => channel.name === 'mod-logs');
|
||||
|
||||
if (!banEntry) return;
|
||||
|
||||
const { target, executor, reason } = banEntry;
|
||||
|
||||
if (!modLogsChannel) return;
|
||||
|
||||
// Call modLogEmbed and send the embed
|
||||
const embed = modLogEmbed("ban", member, reason || "No reason provided");
|
||||
|
||||
if (embed) {
|
||||
await modLogsChannel.send({ embeds: [embed] });
|
||||
//console.log(`Ban logged: ${target.tag} banned by ${executor.tag}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
};
|
||||
48
events/guildBanAdd/logtodb.js
Normal file
48
events/guildBanAdd/logtodb.js
Normal file
@ -0,0 +1,48 @@
|
||||
const { Events, GuildMember } = require("discord.js");
|
||||
const db = require("../../db");
|
||||
|
||||
/**
|
||||
* @param {Client} client
|
||||
* @param {GuildMember} member
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
name: Events.GuildBanAdd,
|
||||
async execute(member) {
|
||||
const auditLogs = await member.guild.fetchAuditLogs({ type: 22 });
|
||||
const banEntry = auditLogs.entries.first();
|
||||
|
||||
try {
|
||||
if (banEntry) {
|
||||
const { reason, executor, target, createdAt } = banEntry;
|
||||
|
||||
const guildID = member.guild.id;
|
||||
const guildName = member.guild.name;
|
||||
const userID = target.id;
|
||||
const userName = target.username;
|
||||
const userTag = target.tag;
|
||||
const avatarURL = target.displayAvatarURL({ dynamic: true });
|
||||
const banDate = createdAt.toISOString();
|
||||
const banReason = reason || "No reason provided";
|
||||
const banExecutor = executor.tag;
|
||||
|
||||
await db.query(
|
||||
"INSERT INTO bans (guild_ID, guild_Name, user_ID, username, user_Tag, avatar_URL, ban_Date, ban_Reason, ban_Executor) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
[
|
||||
guildID,
|
||||
guildName,
|
||||
userID,
|
||||
userName,
|
||||
userTag,
|
||||
avatarURL,
|
||||
banDate,
|
||||
banReason,
|
||||
banExecutor,
|
||||
]
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("There was an error", error);
|
||||
}
|
||||
},
|
||||
};
|
||||
33
events/guildBanRemove/chatlog.js
Normal file
33
events/guildBanRemove/chatlog.js
Normal file
@ -0,0 +1,33 @@
|
||||
const { Events, GuildMember } = require("discord.js");
|
||||
const { modLogEmbed } = require("../../embeds/modLogs");
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
name: Events.GuildBanRemove,
|
||||
async execute(member) {
|
||||
try {
|
||||
const auditLogs = await member.guild.fetchAuditLogs({ type: 23 });
|
||||
const banEntry = auditLogs.entries.first();
|
||||
const modLogsChannel = member.guild.channels.cache.find(channel => channel.name === 'mod-logs');
|
||||
|
||||
if (!banEntry) return;
|
||||
|
||||
const { target, executor, reason } = banEntry;
|
||||
|
||||
if (!modLogsChannel) return;
|
||||
|
||||
// Call modLogEmbed and send the embed
|
||||
const embed = modLogEmbed("unban", member, reason || "No reason provided");
|
||||
|
||||
if (embed) {
|
||||
await modLogsChannel.send({ embeds: [embed] });
|
||||
//console.log(`Ban logged: ${target.tag} banned by ${executor.tag}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
};
|
||||
21
events/guildCreate/addGuildToDB.js
Normal file
21
events/guildCreate/addGuildToDB.js
Normal file
@ -0,0 +1,21 @@
|
||||
const { Guild, Events } = require('discord.js');
|
||||
|
||||
const db = require('../../db');
|
||||
|
||||
module.exports = {
|
||||
name: Events.GuildCreate,
|
||||
async execute(guild) {
|
||||
try {
|
||||
await db.query('CREATE TABLE IF NOT EXISTS guilds (guild_id VARCHAR PRIMARY KEY, guild_name VARCHAR, created_at TIMESTAMP, guild_owner_id VARCHAR)');
|
||||
const result = await db.query('SELECT * FROM guilds WHERE guild_id = $1', [guild.id]);
|
||||
if (result.rows.length === 0) {
|
||||
await db.query('INSERT INTO guilds (guild_id, guild_name, created_at, guild_owner_id) VALUES ($1, $2, $3, $4)', [guild.id, guild.name, guild.createdAt, guild.ownerId]);
|
||||
console.log(`Added new guild to the database: ${guild.name}`);
|
||||
} else {
|
||||
console.log(`Guild already exists in the database: ${guild.name}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error adding guild to the database:', error);
|
||||
}
|
||||
},
|
||||
};
|
||||
34
events/guildCreate/createChannel.js
Normal file
34
events/guildCreate/createChannel.js
Normal file
@ -0,0 +1,34 @@
|
||||
const { Guild, Events, PermissionFlagsBits } = require('discord.js');
|
||||
|
||||
const db = require('../../db');
|
||||
|
||||
module.exports = {
|
||||
name: Events.GuildCreate,
|
||||
async execute(guild) {
|
||||
try {
|
||||
await db.query('CREATE TABLE IF NOT EXISTS bot_channel (guild_id VARCHAR PRIMARY KEY, guild_name VARCHAR, channel_id VARCHAR, channel_name VARCHAR)');
|
||||
const result = await db.query('SELECT * FROM bot_channel WHERE guild_id = $1', [guild.id]);
|
||||
if (result.rows.length === 0) {
|
||||
// Create Channel then add to database
|
||||
const channel = await guild.channels.create({
|
||||
name: 'BluBot',
|
||||
type: 0,
|
||||
permissionOverwrites: [
|
||||
{
|
||||
id: guild.id,
|
||||
allow: [PermissionFlagsBits.ViewChannel],
|
||||
},
|
||||
],
|
||||
});
|
||||
await db.query('INSERT INTO bot_channel (guild_id, guild_name, channel_id, channel_name) VALUES ($1, $2, $3, $4)', [guild.id, guild.name, channel.id, channel.name]);
|
||||
//await db.query('INSERT INTO bot_channel (guild_id, guild_name, channel_id, channel_name) VALUES ($1, $2, $3, $4)', [guild.id, guild.name, guild.channels.id, guild.channels.name]);
|
||||
console.log(`Added new guild to the database: ${guild.name}`);
|
||||
} else {
|
||||
console.log(`Guild already exists in the database: ${guild.name}`);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error creating channel or adding to database:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
26
events/guildMemberAdd/logToDB.js
Normal file
26
events/guildMemberAdd/logToDB.js
Normal file
@ -0,0 +1,26 @@
|
||||
const { Events, GuildMember } = require("discord.js");
|
||||
const db = require("../../db");
|
||||
|
||||
/**
|
||||
* @param {Client} client
|
||||
* @param {GuildMember} member
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
name: Events.GuildMemberAdd,
|
||||
async execute(member) {
|
||||
try {
|
||||
const userID = member.id;
|
||||
const userName = member.user.username;
|
||||
const userTag = member.user.tag;
|
||||
const avatarURL = member.displayAvatarURL({ dynamic: true });
|
||||
|
||||
await db.query(
|
||||
"INSERT INTO members (user_ID, username, user_Tag, avatar_URL) VALUES ($1, $2, $3, $4) ON CONFLICT (user_ID) DO NOTHING",
|
||||
[userID, userName, userTag, avatarURL]
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("There was an error", error);
|
||||
}
|
||||
},
|
||||
};
|
||||
34
events/guildMemberAdd/sendWelcome.js
Normal file
34
events/guildMemberAdd/sendWelcome.js
Normal file
@ -0,0 +1,34 @@
|
||||
const { GuildMember, Events } = require('discord.js')
|
||||
const db = require('../../db')
|
||||
const { welcomeEmbed } = require('../../embeds/welcomeMember')
|
||||
|
||||
/**
|
||||
* @param {Client} client
|
||||
* @param {GuildMember} member
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
name: Events.GuildMemberAdd,
|
||||
async execute(member) {
|
||||
|
||||
try {
|
||||
const embed = welcomeEmbed(member)
|
||||
const result = await db.query(`SELECT channel_id, enabled FROM auto_welcome WHERE guild_id = $1`,
|
||||
[member.guild.id])
|
||||
|
||||
const welcomeData = result.rows[0]
|
||||
|
||||
if (welcomeData && welcomeData.enabled) {
|
||||
const welcomeChannel = member.guild.channels.cache.get(welcomeData.channel_id)
|
||||
|
||||
if (welcomeChannel) {
|
||||
welcomeChannel.send({ embeds: [embed]})
|
||||
} else {
|
||||
console.error('Invalid channel id')
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('There was an error in sendWelcome:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
26
events/messageCreate/messageCreate.js
Normal file
26
events/messageCreate/messageCreate.js
Normal file
@ -0,0 +1,26 @@
|
||||
const { Events, Message } = require('discord.js');
|
||||
const db = require('../../db')
|
||||
|
||||
/**
|
||||
* @param { Message } message
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
name: Events.MessageCreate,
|
||||
async execute(message) {
|
||||
const messageContent = message.content
|
||||
const author = message.author.tag
|
||||
const guildID = message.guild.id
|
||||
if (!message.inGuild() || message.author.bot) return;
|
||||
|
||||
try {
|
||||
await db.query("INSERT INTO messages (message, author, guild_ID) VALUES ($1, $2, $3)", [
|
||||
messageContent,
|
||||
author,
|
||||
guildID,
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error("Error saving message: ", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
47
events/presenceUpdate/checktwitchPres.js
Normal file
47
events/presenceUpdate/checktwitchPres.js
Normal file
@ -0,0 +1,47 @@
|
||||
const { Guild, GuildMember, Events } = require('discord.js')
|
||||
const db = require('../../db')
|
||||
|
||||
const { userStreamingEmbed } = require('../../embeds/userStreaming')
|
||||
|
||||
|
||||
module.exports = {
|
||||
name: Events.PresenceUpdate,
|
||||
async execute(oldPresence, newPresence) {
|
||||
|
||||
//console.log('Presence Update:', newPresence);
|
||||
|
||||
|
||||
|
||||
// Check if the user is streaming
|
||||
if (!newPresence.activities) return;
|
||||
const streamingActivity = newPresence.activities.find(activity => activity.type === 1);
|
||||
if (!streamingActivity) return;
|
||||
|
||||
const user = newPresence.user;
|
||||
|
||||
if (streamingActivity)
|
||||
{
|
||||
try {
|
||||
for (const [guildId, guild] of newPresence.client.guilds.cache) {
|
||||
const member = await guild.members.fetch(user.id).catch(() => null);
|
||||
if (!member) continue;
|
||||
const result = await db.query("SELECT channel_id FROM bot_channel WHERE guild_id = $1", [guildId]);
|
||||
if (result.rows.length === 0) continue;
|
||||
const channelId = result.rows[0].channel_id;
|
||||
const channel = guild.channels.cache.get(channelId);
|
||||
|
||||
if (channel && channel.isTextBased()) {
|
||||
const { embeds, components } = userStreamingEmbed(user, streamingActivity);
|
||||
await channel.send({
|
||||
embeds,
|
||||
components
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching user:', error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
10
events/ready/ready.js
Normal file
10
events/ready/ready.js
Normal file
@ -0,0 +1,10 @@
|
||||
const { Events, PresenceUpdateStatus } = require('discord.js')
|
||||
|
||||
module.exports = {
|
||||
name: Events.ClientReady,
|
||||
once: true,
|
||||
execute(client) {
|
||||
console.log(`Ready! Logged in as ${client.user.tag}`);
|
||||
client.user.setStatus(PresenceUpdateStatus.Online)
|
||||
},
|
||||
};
|
||||
41
events/ready/setActivity.js
Normal file
41
events/ready/setActivity.js
Normal file
@ -0,0 +1,41 @@
|
||||
const { Client, Guild, Events, ActivityType, PresenceUpdateStatus } = require('discord.js');
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Client} client
|
||||
* @param {Guild} guild
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
name: Events.ClientReady,
|
||||
async execute(client) {
|
||||
const guildsCount = client.guilds.cache.size;
|
||||
|
||||
let status = [
|
||||
{
|
||||
name: "I am Live",
|
||||
type: ActivityType.Watching,
|
||||
},
|
||||
{
|
||||
name: "Choo Choo 🚂",
|
||||
},
|
||||
{
|
||||
name: 'Hippity Hoppity',
|
||||
},
|
||||
{
|
||||
name: `I am in ${guildsCount} Server(s)`,
|
||||
},
|
||||
{
|
||||
name: 'Yippe 🐻'
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
|
||||
|
||||
setInterval(() => {
|
||||
let random = Math.floor(Math.random() * status.length);
|
||||
client.user.setActivity(status[random]);
|
||||
}, 30000);
|
||||
}
|
||||
};
|
||||
0
events/voiceStateUpdate/checkUpdate.js
Normal file
0
events/voiceStateUpdate/checkUpdate.js
Normal file
73
index.js
Normal file
73
index.js
Normal file
@ -0,0 +1,73 @@
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path')
|
||||
const {Client, Events, GatewayIntentBits, Collection, InteractionResponse } = require('discord.js');
|
||||
require('dotenv').config();
|
||||
const { connectDB, closeDB } = require('./db')
|
||||
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, GatewayIntentBits.GuildModeration, GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildPresences ] });
|
||||
|
||||
client.commands= new Collection();
|
||||
|
||||
const foldersPath = path.join(__dirname, 'commands');
|
||||
const commandFolders = fs.readdirSync(foldersPath);
|
||||
|
||||
for (const folder of commandFolders) {
|
||||
const commandsPath = path.join(foldersPath, folder);
|
||||
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
|
||||
for (const file of commandFiles) {
|
||||
const filePath = path.join(commandsPath, file);
|
||||
const command = require(filePath);
|
||||
// Set a new item in the Collection with the key as the command name and the value as the exported module
|
||||
if ('data' in command && 'execute' in command) {
|
||||
client.commands.set(command.data.name, command);
|
||||
} else {
|
||||
console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function loadEvents(dir, client) {
|
||||
const files = fs.readdirSync(dir, { withFileTypes: true });
|
||||
|
||||
for (const file of files) {
|
||||
const fullPath = path.join(dir, file.name);
|
||||
|
||||
if (file.isDirectory()) {
|
||||
// Recursively load events from subdirectories
|
||||
loadEvents(fullPath, client);
|
||||
} else if (file.isFile() && file.name.endsWith('.js')) {
|
||||
// Load the event file
|
||||
const event = require(fullPath);
|
||||
|
||||
if (event.once) {
|
||||
client.once(event.name, (...args) => event.execute(...args));
|
||||
} else {
|
||||
client.on(event.name, (...args) => event.execute(...args));
|
||||
}
|
||||
|
||||
//console.log(`Loaded event: ${event.name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call the function to load all events
|
||||
const eventsPath = path.join(__dirname, 'events');
|
||||
loadEvents(eventsPath, client);
|
||||
|
||||
|
||||
connectDB().then(() => {
|
||||
client.login(process.env.TOKEN).catch(console.error)
|
||||
});
|
||||
|
||||
|
||||
process.on('SIGINT', async () => {
|
||||
console.log('Bot is shutting down...')
|
||||
closeDB();
|
||||
process.exit(0)
|
||||
});
|
||||
|
||||
process.on('SIGTERM', async () => {
|
||||
console.log('Bot received termination signal...');
|
||||
closeDB();
|
||||
process.exit(0)
|
||||
})
|
||||
1545
package-lock.json
generated
Normal file
1545
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
29
package.json
Normal file
29
package.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "blubot",
|
||||
"version": "1.1.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"run": "node .",
|
||||
"dev": "nodemon ."
|
||||
},
|
||||
"author": "Megabyte",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"@discordjs/voice": "^0.17.0",
|
||||
"@distube/ytdl-core": "^4.14.4",
|
||||
"axios": "^1.7.7",
|
||||
"discord-api-types": "^0.37.119",
|
||||
"discord.js": "^14.18.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"ffmpeg": "^0.0.4",
|
||||
"ffmpeg-static": "^5.2.0",
|
||||
"libsodium-wrappers": "^0.7.15",
|
||||
"nodemon": "^3.1.9",
|
||||
"opusscript": "^0.0.8",
|
||||
"pg": "^8.13.0",
|
||||
"play-dl": "^1.9.7",
|
||||
"undici": "^7.7.0",
|
||||
"ytdl-core": "^4.11.5"
|
||||
}
|
||||
}
|
||||
171
playback.js
Normal file
171
playback.js
Normal file
@ -0,0 +1,171 @@
|
||||
const { Client, Interaction, Member } = require("discord.js");
|
||||
const {
|
||||
joinVoiceChannel,
|
||||
createAudioPlayer,
|
||||
createAudioResource,
|
||||
NoSubscriberBehavior,
|
||||
AudioPlayerStatus,
|
||||
} = require("@discordjs/voice");
|
||||
const ytdl = require("@distube/ytdl-core");
|
||||
const { nowPlayingEmbed } = require("./embeds/nowPlaying");
|
||||
|
||||
const queue = [];
|
||||
const activeChannels = {};
|
||||
let stopped = false;
|
||||
let timeout;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Client} client
|
||||
* @param {Interaction} interaction
|
||||
*/
|
||||
|
||||
function resetAll(guildId) {
|
||||
//console.log(`Resetting all: ${guildId}`)
|
||||
queue.length = 0;
|
||||
cancelTimer()
|
||||
const connection = activeChannels[guildId]
|
||||
if (connection) {
|
||||
connection.destroy()
|
||||
// console.log('Conection Destroyed')
|
||||
}
|
||||
delete activeChannels[guildId]
|
||||
stopped = false
|
||||
//console.log('All States reset')
|
||||
}
|
||||
|
||||
function startTimer(connection) {
|
||||
console.log(`Disconnect timer started `);
|
||||
timeout = setTimeout(() => {
|
||||
connection.destroy();
|
||||
}, 30_000);
|
||||
}
|
||||
|
||||
function cancelTimer() {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
console.log("Disconnect timer stopped");
|
||||
timeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function playFromQueue(
|
||||
voiceChannel,
|
||||
textChannel,
|
||||
getMemeber,
|
||||
videoUrl,
|
||||
videoTitle
|
||||
) {
|
||||
if (queue.length === 0) {
|
||||
if (textChannel && activeChannels[voiceChannel.id] === textChannel.id) {
|
||||
textChannel.send("Queue has finished playing.");
|
||||
delete activeChannels[voiceChannel.id];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//console.log("Current queue: ", queue)
|
||||
|
||||
const { interaction, link, songName } = queue[0];
|
||||
|
||||
try {
|
||||
let connection = activeChannels[interaction.guild.id];
|
||||
// Join the voice channel
|
||||
|
||||
if (!connection) {
|
||||
connection = joinVoiceChannel({
|
||||
channelId: voiceChannel.id,
|
||||
guildId: interaction.guild.id,
|
||||
adapterCreator: interaction.guild.voiceAdapterCreator,
|
||||
});
|
||||
activeChannels[interaction.guild.id] = connection;
|
||||
}
|
||||
|
||||
// Stream audio using ytdl-core
|
||||
const stream = ytdl(videoUrl, {
|
||||
filter: "audioonly",
|
||||
highWaterMark: 1 << 25, // Larger buffer size
|
||||
quality: "highestaudio",
|
||||
requestOptions: {
|
||||
headers: { Connection: "keep-alive" },
|
||||
},
|
||||
});
|
||||
const resource = createAudioResource(stream, { inlineVolume: true });
|
||||
resource.volume.setVolume(1); // Set volume to 1 (max)
|
||||
|
||||
const player = createAudioPlayer({
|
||||
behaviors: {
|
||||
noSubscriber: NoSubscriberBehavior.Play,
|
||||
},
|
||||
});
|
||||
|
||||
player.play(resource);
|
||||
connection.subscribe(player);
|
||||
|
||||
player.on(AudioPlayerStatus.Playing, () => {
|
||||
if (startTimer) {
|
||||
cancelTimer()
|
||||
} else {
|
||||
console.log('No timer running')
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
player.on(AudioPlayerStatus.Idle, () => {
|
||||
//console.log('The audio player is idle.');
|
||||
if (stopped) {
|
||||
console.log("Playback was stopped by user");
|
||||
return;
|
||||
}
|
||||
|
||||
queue.shift();
|
||||
if (queue.length > 0) {
|
||||
const nextsong = queue[0];
|
||||
playFromQueue(
|
||||
voiceChannel,
|
||||
textChannel,
|
||||
nextsong.getMember,
|
||||
nextsong.link,
|
||||
nextsong.songName
|
||||
);
|
||||
} else {
|
||||
textChannel.send("The queue is empty.");
|
||||
if (connection) {
|
||||
startTimer(connection);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (interaction.replied || interaction.deferred) {
|
||||
const embed = nowPlayingEmbed(queue);
|
||||
await interaction.followUp({ embeds: [embed] });
|
||||
} else {
|
||||
const embed = nowPlayingEmbed(queue);
|
||||
await interaction.reply({ embeds: [embed] });
|
||||
}
|
||||
|
||||
// Handle errors
|
||||
player.on("error", (error) => {
|
||||
console.error("Error with the audio player:", error);
|
||||
interaction.followUp({
|
||||
content: "There was an error playing the music.",
|
||||
ephemeral: true,
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error in playback.js: ", error);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
playFromQueue,
|
||||
queue,
|
||||
cancelTimer,
|
||||
setStopped: (value) => {
|
||||
stopped = value;
|
||||
},
|
||||
getConnection: (guildId) => activeChannels[guildId], // Helper to get connection
|
||||
resetAll,
|
||||
};
|
||||
Reference in New Issue
Block a user