Compare commits

..

No commits in common. 'Slash-V14' and 'Slash-V14' have entirely different histories.

@ -2,7 +2,7 @@ token=YourToken
clientId=BotClientId
guildId=DevGuildId
ownerId=OwnerUserId
statusChannel=CHannelIdForStatus
statusChannel=
uptimeURL=UptimeKumaOrWhateverStatusThingYouUseOrJustLeaveEmpty
uptimeInterval=60
twiConsumer=TwitterConsumerToken
@ -14,6 +14,4 @@ twiLogChannel=ChannelWhereTheDetailedInfoOfTheCommandIsSent
botsggToken=APITokenForBots.gg
botsggEndpoint=https://discord.bots.gg/api/v1
stableHordeApi=0000000000
stableHordeID=0000
NODE_ENV=development
ytdlpMaxResolution=720
stableHordeID=0000

@ -33,5 +33,5 @@ labels:
**Did someone already report that bug?**
- [ ] Yes <!-- If you have to put yes you don't need to submit that bug report. -->
- [ ] Yes <!-- If you have to put yes you don't need to submit that feature request. -->
- [ ] No

13
.gitignore vendored

@ -1,16 +1,7 @@
.env
node_modules/
bin/
config/config.json
json/board/
unloaded/
database.sqlite3
tmp/*.js
bin/yt-dlp
bin/HandBrakeCLI
bin/upload.sh
bin/dectalk
asset/ytp/sources
asset/ytp/music
asset/ytp/sounds
database.sqlite3

@ -27,7 +27,6 @@ export default {
.setDescription('What do you want the AI to generate?')
.setRequired(true)),
category: 'AI',
alias: ['i2i'],
async execute(interaction, args, client) {
await interaction.deferReply();
@ -78,12 +77,6 @@ async function generate(i, prompt, client, b64Img) {
let response = await fetch('https://stablehorde.net/api/v2/generate/async', fetchParameters);
response = await response.json();
if (!response.id) {
console.log(response);
return i.editReply({ content: `An error has occured, please try again later. \`${response.message}\`` });
}
let wait_time = 5000;
let checkURL = `https://stablehorde.net/api/v2/generate/check/${response.id}`;
const checking = setInterval(async () => {
@ -91,13 +84,8 @@ async function generate(i, prompt, client, b64Img) {
if (checkResult === undefined) return;
if (!checkResult.done) {
if (checkResult.wait_time === -1) {
console.log(checkResult.raw);
return i.editReply({ content: `An error has occured, please try again later. \`${checkResult.raw.message}\`` });
}
if (checkResult.wait_time < 0) {
console.log(checkResult.raw);
console.log(checkResult);
clearInterval(checking);
return i.editReply({ content: 'No servers are currently available to fulfill your request, please try again later.' });
}
@ -128,14 +116,21 @@ async function generate(i, prompt, client, b64Img) {
const row = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(`regenerate${i.user.id}${i.id}`)
.setCustomId(`regenerate${i.user.id}`)
.setLabel('🔄 Regenerate')
.setStyle(ButtonStyle.Primary),
);
await i.editReply({ embeds: [stableEmbed], components: [row], files: [generatedImg] });
listenButton(client, i, prompt);
client.once('interactionCreate', async (interactionMenu) => {
if (i.user !== interactionMenu.user) return;
if (!interactionMenu.isButton) return;
if (interactionMenu.customId === `regenerate${interactionMenu.user.id}`) {
await interactionMenu.deferReply();
await generate(interactionMenu, prompt, client);
}
});
}
}, wait_time);
}
@ -145,27 +140,14 @@ async function checkGeneration(url) {
check = await check.json();
if (!check.is_possible) {
return { done: false, wait_time: -1, raw: check };
return { done: false, wait_time: -1 };
}
if (check.done) {
if (!check.generations) {
return { done: false, wait_time: check.wait_time * 1000, raw: check };
return { done: false, wait_time: check.wait_time * 1000 };
}
return { done: true, image: check.generations[0].img, seed: check.generations[0].seed, worker_id: check.generations[0].worker_id, worker_name: check.generations[0].worker_name, raw: check };
return { done: true, image: check.generations[0].img, seed: check.generations[0].seed, worker_id: check.generations[0].worker_id, worker_name: check.generations[0].worker_name };
}
}
async function listenButton(client, interaction, prompt) {
client.once('interactionCreate', async (interactionMenu) => {
if (!interactionMenu.isButton()) return;
await interactionMenu.update({ components: [] });
if (interactionMenu.customId === `regenerate${interactionMenu.user.id}${interaction.id}`) {
await interactionMenu.deferReply();
await generate(interactionMenu, prompt, client);
}
});
}

@ -23,7 +23,6 @@ export default {
.setDescription('What do you want the AI to generate?')
.setRequired(true)),
category: 'AI',
alias: ['t2i'],
async execute(interaction, args, client) {
await interaction.deferReply();
generate(interaction, args.prompt, client);
@ -102,14 +101,21 @@ async function generate(i, prompt, client) {
const row = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(`regenerate${i.user.id}${i.id}`)
.setCustomId(`regenerate${i.user.id}`)
.setLabel('🔄 Regenerate')
.setStyle(ButtonStyle.Primary),
);
await i.editReply({ embeds: [stableEmbed], components: [row], files: [generatedImg] });
listenButton(client, i, prompt);
client.once('interactionCreate', async (interactionMenu) => {
if (i.user !== interactionMenu.user) return;
if (!interactionMenu.isButton) return;
if (interactionMenu.customId === `regenerate${interactionMenu.user.id}`) {
await interactionMenu.deferReply();
await generate(interactionMenu, prompt, client);
}
});
}
}, wait_time);
}
@ -130,16 +136,3 @@ async function checkGeneration(url) {
return { done: true, image: check.generations[0].img, seed: check.generations[0].seed, worker_id: check.generations[0].worker_id, worker_name: check.generations[0].worker_name };
}
}
async function listenButton(client, interaction, prompt) {
client.once('interactionCreate', async (interactionMenu) => {
if (!interactionMenu.isButton()) return;
await interactionMenu.update({ components: [] });
if (interactionMenu.customId === `regenerate${interactionMenu.user.id}${interaction.id}`) {
await interactionMenu.deferReply();
await generate(interactionMenu, prompt, client);
}
});
}

@ -19,13 +19,13 @@ export default {
const row = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(`yes${interaction.user.id}${interaction.id}`)
.setCustomId(`yes${interaction.user.id}`)
.setLabel('Yes')
.setStyle(ButtonStyle.Primary),
)
.addComponents(
new ButtonBuilder()
.setCustomId(`no${interaction.user.id}${interaction.id}`)
.setCustomId(`no${interaction.user.id}`)
.setLabel('No')
.setStyle(ButtonStyle.Danger),
);
@ -39,24 +39,18 @@ export default {
return interaction.editReply({ content: 'Auto response has been enabled.', ephemeral: true });
}
return listenButton(client, interaction, interaction.user);
client.on('interactionCreate', async (interactionMenu) => {
if (interaction.user !== interactionMenu.user) return;
if (!interactionMenu.isButton) return;
interactionMenu.update({ components: [] });
if (interactionMenu.customId === `yes${interaction.user.id}`) {
const body = { serverID: interaction.guild.id, stat: 'disable' };
await db.autoresponseStat.update(body, { where: { serverID: interaction.guild.id } });
return interaction.editReply({ content: 'Auto response has been disabled.', ephemeral: true });
}
else {
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
}
});
},
};
async function listenButton(client, interaction, user = interaction.user, originalId = interaction.id) {
client.once('interactionCreate', async (interactionMenu) => {
if (user !== interactionMenu.user) return listenButton(client, interaction, user, originalId);
if (!interactionMenu.isButton()) return;
await interactionMenu.update({ components: [] });
if (interactionMenu.customId === `yes${interaction.user.id}${originalId}`) {
const body = { serverID: interaction.guild.id, stat: 'disable' };
await db.autoresponseStat.update(body, { where: { serverID: interaction.guild.id } });
return interaction.editReply({ content: 'Auto response has been disabled.', ephemeral: true });
}
else {
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
}
});
}
};

@ -25,50 +25,44 @@ export default {
const row = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(`edit${interaction.user.id}${interaction.id}`)
.setCustomId(`edit${interaction.user.id}`)
.setLabel('Edit')
.setStyle(ButtonStyle.Primary),
)
.addComponents(
new ButtonBuilder()
.setCustomId(`remove${interaction.user.id}${interaction.id}`)
.setCustomId(`remove${interaction.user.id}`)
.setLabel('Remove')
.setStyle(ButtonStyle.Danger),
)
.addComponents(
new ButtonBuilder()
.setCustomId(`nothing${interaction.user.id}${interaction.id}`)
.setCustomId(`nothing${interaction.user.id}`)
.setLabel('Do nothing')
.setStyle(ButtonStyle.Secondary),
);
await interaction.reply({ content: 'The server already has a message set, do you want to edit it or remove it?', components: [row], ephemeral: true });
return listenButton(client, interaction, args, interaction.user);
client.on('interactionCreate', async (interactionMenu) => {
if (interaction.user !== interactionMenu.user) return;
if (!interactionMenu.isButton) return;
interactionMenu.update({ components: [] });
if (interactionMenu.customId === `edit${interaction.user.id}`) {
if (!args.message) {
return interaction.reply({ content: 'You need to input a message for me to edit!', ephemeral: true });
}
const body = { guildID: interaction.guild.id, channelID: interaction.channel.id, message: args.message };
await db.leaveChannel.update(body, { where: { guildID: interaction.guild.id } });
return interaction.editReply({ content: `The leave message has been set to ${args.message}`, ephemeral: true });
}
else if (interactionMenu.customId === `remove${interaction.user.id}`) {
db.leaveChannel.destroy({ where: { guildID: interaction.guild.id, channelID: interaction.channel.id } });
return interaction.editReply({ content: 'The leave message has been deleted.', ephemeral: true });
}
else {
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
}
});
},
};
async function listenButton(client, interaction, args, user = interaction.user, originalId = interaction.id) {
client.once('interactionCreate', async (interactionMenu) => {
if (user !== interactionMenu.user) return listenButton(client, interaction, args, user, originalId);
if (!interactionMenu.isButton()) return;
await interactionMenu.update({ components: [] });
if (interactionMenu.customId === `edit${interaction.user.id}${originalId}`) {
if (!args.message) {
return interaction.reply({ content: 'You need to input a message for me to edit!', ephemeral: true });
}
const body = { guildID: interaction.guild.id, channelID: interaction.channel.id, message: args.message };
await db.leaveChannel.update(body, { where: { guildID: interaction.guild.id } });
return interaction.editReply({ content: `The leave message has been set to ${args.message}`, ephemeral: true });
}
else if (interactionMenu.customId === `remove${interaction.user.id}${originalId}`) {
db.leaveChannel.destroy({ where: { guildID: interaction.guild.id, channelID: interaction.channel.id } });
return interaction.editReply({ content: 'The leave message has been deleted.', ephemeral: true });
}
else {
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
}
});
}

@ -20,13 +20,13 @@ export default {
const row = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(`yes${interaction.user.id}${interaction.id}`)
.setCustomId(`yes${interaction.user.id}`)
.setLabel('Yes')
.setStyle(ButtonStyle.Primary),
)
.addComponents(
new ButtonBuilder()
.setCustomId(`no${interaction.user.id}${interaction.id}`)
.setCustomId(`no${interaction.user.id}`)
.setLabel('No')
.setStyle(ButtonStyle.Danger),
);
@ -40,23 +40,18 @@ export default {
return interaction.editReply({ content: 'Quotation has been enabled.', ephemeral: true });
}
return listenButton(client, interaction, interaction.user);
client.on('interactionCreate', async (interactionMenu) => {
if (interaction.user !== interactionMenu.user) return;
if (!interactionMenu.isButton) return;
interactionMenu.update({ components: [] });
if (interactionMenu.customId === `yes${interaction.user.id}`) {
const body = { serverID: interaction.guild.id, stat: 'disable' };
await db.quotationStat.update(body, { where: { serverID: interaction.guild.id } });
return interaction.editReply({ content: 'Quotation has been disabled.', ephemeral: true });
}
else {
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
}
});
},
};
async function listenButton(client, interaction, user = interaction.user, originalId = interaction.id) {
client.once('interactionCreate', async (interactionMenu) => {
if (user !== interactionMenu.user) return listenButton(client, interaction, user, originalId);
if (!interactionMenu.isButton()) return;
interactionMenu.update({ components: [] });
if (interactionMenu.customId === `yes${interaction.user.id}${originalId}`) {
await db.quotationStat.destroy({ where: { serverID: interaction.guild.id } });
return interaction.editReply({ content: 'Quotation has been disabled.', ephemeral: true });
}
else {
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
}
});
}

@ -6,7 +6,7 @@ export default {
.setName('shameboard')
.setDescription('Set shameboard to the current channel.')
.addStringOption(option =>
option.setName('emote')
option.setName('emote')
.setDescription('The emote that should be used to enter the shameboard.'))
.addStringOption(option =>
option.setName('count')

@ -1,4 +1,4 @@
import { SlashCommandBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder, PermissionFlagsBits, PermissionsBitField } from 'discord.js';
import { SlashCommandBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder, PermissionFlagsBits } from 'discord.js';
import os from 'node:os';
import fs from 'node:fs';
@ -51,12 +51,12 @@ export default {
if (args.remove) {
if (tag) {
if (tag.get('ownerID') == interaction.user.id || interaction.member.permissionsIn(interaction.channel).has(PermissionsBitField.Flags.Administrator) || interaction.user.id == ownerId) {
if (tag.get('ownerID') == interaction.user.id || interaction.member.permissionsIn(interaction.channel).has('ADMINISTRATOR') || interaction.user.id == ownerId) {
db.Tag.destroy({ where: { trigger: args.trigger, serverID: interaction.guild.id } });
return interaction.editReply('successfully deleted the following tag: ' + args.trigger);
}
else {
return interaction.editReply(`You are not the owner of this tag, if you think it is problematic ask a user with the 'Administrator' permission to remove it by doing ${this.client.commandHandler.prefix[0]}tag ${args.trigger} --remove`);
return interaction.editReply(`You are not the owner of this tag, if you think it is problematic ask an admin to remove it by doing ${this.client.commandHandler.prefix[0]}tag ${args.trigger} --remove`);
}
}
else {
@ -77,51 +77,56 @@ export default {
const row = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(`edit${interaction.user.id}${interaction.id}`)
.setCustomId(`edit${interaction.user.id}`)
.setLabel('Edit')
.setStyle(ButtonStyle.Primary),
)
.addComponents(
new ButtonBuilder()
.setCustomId(`remove${interaction.user.id}${interaction.id}`)
.setCustomId(`remove${interaction.user.id}`)
.setLabel('Remove')
.setStyle(ButtonStyle.Danger),
)
.addComponents(
new ButtonBuilder()
.setCustomId(`nothing${interaction.user.id}${interaction.id}`)
.setCustomId(`nothing${interaction.user.id}`)
.setLabel('Do nothing')
.setStyle(ButtonStyle.Secondary),
);
await interaction.editReply({ content: 'This tag already exist, do you want to update it, remove it or do nothing?', components: [row], ephemeral: true });
return listenButton(client, interaction, args, interaction.user);
client.on('interactionCreate', async (interactionMenu) => {
if (interaction.user !== interactionMenu.user) return;
if (!interactionMenu.isButton) return;
interactionMenu.update({ components: [] });
if (interactionMenu.customId === `edit${interaction.user.id}`) {
const body = { trigger: args.trigger, response: args.response, ownerID: interaction.user.id, serverID: interaction.guild.id };
await db.joinChannel.update(body, { where: { guildID: interaction.guild.id } });
return interaction.editReply({ content: `The tag ${args.trigger} has been set to ${args.response}`, ephemeral: true });
}
else if (interactionMenu.customId === `remove${interaction.user.id}`) {
db.Tag.destroy({ where: { trigger: args.trigger, serverID: interaction.guild.id } });
return interaction.editReply({ content: `The tag ${args.trigger} has been deleted`, ephemeral: true });
}
else {
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
}
});
}
else {
return interaction.editReply(`You are not the owner of this tag, if you think it is problematic ask an admin to remove it by doing ${this.client.commandHandler.prefix[0]}tag ${args.trigger} --remove`);
}
},
};
async function listenButton(client, interaction, args, user = interaction.user, originalId = interaction.id) {
client.once('interactionCreate', async (interactionMenu) => {
if (user !== interactionMenu.user) return listenButton(client, interaction, args, user, originalId);
if (!interactionMenu.isButton()) return;
await interactionMenu.update({ components: [] });
const join = await db.joinChannel.findOne({ where: { guildID: interaction.guild.id } });
if (interactionMenu.customId === `edit${interaction.user.id}${originalId}`) {
const body = { trigger: args.trigger, response: args.response, ownerID: interaction.user.id, serverID: interaction.guild.id };
db.Tag.update(body, { where: { serverID: interaction.guild.id } });
return interaction.editReply({ content: `The tag ${args.trigger} has been set to ${args.response}`, ephemeral: true });
if (!join && !args.message) {
return interaction.editReply({ content: 'You need a message for me to say anything!', ephemeral: true });
}
else if (interactionMenu.customId === `remove${interaction.user.id}${originalId}`) {
db.Tag.destroy({ where: { trigger: args.trigger, serverID: interaction.guild.id } });
return interaction.editReply({ content: `The tag ${args.trigger} has been deleted`, ephemeral: true });
else if (!join) {
const body = { guildID: interaction.guild.id, channelID: interaction.channel.id, message: args.message };
await db.joinChannel.create(body);
return interaction.editReply({ content: `The join message have been set with ${args.message}`, ephemeral: true });
}
else {
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
}
});
}
},
};

@ -26,50 +26,44 @@ export default {
const row = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(`edit${interaction.user.id}${interaction.id}`)
.setCustomId(`edit${interaction.user.id}`)
.setLabel('Edit')
.setStyle(ButtonStyle.Primary),
)
.addComponents(
new ButtonBuilder()
.setCustomId(`remove${interaction.user.id}${interaction.id}`)
.setCustomId(`remove${interaction.user.id}`)
.setLabel('Remove')
.setStyle(ButtonStyle.Danger),
)
.addComponents(
new ButtonBuilder()
.setCustomId(`nothing${interaction.user.id}${interaction.id}`)
.setCustomId(`nothing${interaction.user.id}`)
.setLabel('Do nothing')
.setStyle(ButtonStyle.Secondary),
);
await interaction.reply({ content: 'The server already has a message set, do you want to edit it or remove it?', components: [row], ephemeral: true });
return listenButton(client, interaction, args, interaction.user);
client.on('interactionCreate', async (interactionMenu) => {
if (interaction.user !== interactionMenu.user) return;
if (!interactionMenu.isButton) return;
interactionMenu.update({ components: [] });
if (interactionMenu.customId === `edit${interaction.user.id}`) {
if (!args.message) {
return interaction.reply({ content: 'You need to input a message for me to edit!', ephemeral: true });
}
const body = { guildID: interaction.guild.id, channelID: interaction.channel.id, message: args.message };
await db.joinChannel.update(body, { where: { guildID: interaction.guild.id } });
return interaction.editReply({ content: `The join message has been set to ${args.message}`, ephemeral: true });
}
else if (interactionMenu.customId === `remove${interaction.user.id}`) {
db.joinChannel.destroy({ where: { guildID: interaction.guild.id, channelID: interaction.channel.id } });
return interaction.editReply({ content: 'The join message has been deleted.', ephemeral: true });
}
else {
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
}
});
},
};
async function listenButton(client, interaction, args, user = interaction.user, originalId = interaction.id) {
client.once('interactionCreate', async (interactionMenu) => {
if (user !== interactionMenu.user) return listenButton(client, interaction, args, user, originalId);
if (!interactionMenu.isButton()) return;
await interactionMenu.update({ components: [] });
if (interactionMenu.customId === `edit${interaction.user.id}${originalId}`) {
if (!args.message) {
return interaction.reply({ content: 'You need to input a message for me to edit!', ephemeral: true });
}
const body = { guildID: interaction.guild.id, channelID: interaction.channel.id, message: args.message };
await db.joinChannel.update(body, { where: { guildID: interaction.guild.id } });
return interaction.editReply({ content: `The join message has been set to ${args.message}`, ephemeral: true });
}
else if (interactionMenu.customId === `remove${interaction.user.id}${originalId}`) {
db.joinChannel.destroy({ where: { guildID: interaction.guild.id, channelID: interaction.channel.id } });
return interaction.editReply({ content: 'The join message has been deleted.', ephemeral: true });
}
else {
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
}
});
}

@ -1,60 +0,0 @@
/* TODO
*
* Merge with commands/fun/image2audio.js
*
*/
import { SlashCommandBuilder } from 'discord.js';
import fs from 'node:fs';
import os from 'node:os';
import fetch from 'node-fetch';
import util from 'node:util';
import stream from 'node:stream';
import utils from '../../utils/videos.js';
export default {
data: new SlashCommandBuilder()
.setName('audio2image')
.setDescription('Transform an audio file into an image.')
.addAttachmentOption(option =>
option.setName('audio')
.setDescription('The audio that will become image.')
.setRequired(true)),
category: 'fun',
alias: ['a2i'],
async execute(interaction, args) {
if (!args.audio) return interaction.reply('Please attach an image with your message.');
await interaction.deferReply();
ifExistDelete(`${os.tmpdir()}/${args.audio.name}`);
ifExistDelete(`${os.tmpdir()}/${args.audio.name}.png`);
ifExistDelete(`${os.tmpdir()}/${args.audio.name}.sw`);
ifExistDelete(`${os.tmpdir()}/${args.audio.name}.mp3`);
const streamPipeline = util.promisify(stream.pipeline);
const res = await fetch(args.audio.url);
if (!res.ok) return interaction.editReply('An error has occured while trying to download your image.');
await streamPipeline(res.body, fs.createWriteStream(`${os.tmpdir()}/${args.audio.name}`));
await utils.ffmpeg(['-i', `${os.tmpdir()}/${args.audio.name}`, '-sample_rate', '44100', '-ac', '1', '-f', 's16le', '-acodec', 'pcm_s16le', `${os.tmpdir()}/${args.audio.name}.sw`]);
await utils.ffmpeg(['-pixel_format', 'rgb24', '-video_size', '128x128', '-f', 'rawvideo', '-i', `${os.tmpdir()}/${args.audio.name}.sw`, '-frames:v', '1', `${os.tmpdir()}/${args.audio.name}.png`]);
const file = fs.statSync(`${os.tmpdir()}/${args.audio.name}.png`);
const fileSize = (file.size / 1000000.0).toFixed(2);
if (fileSize > await utils.getMaxFileSize(interaction.guild)) return interaction.editReply('error');
interaction.editReply({ content: `Image file is ${fileSize} MB` });
return interaction.followUp({ files: [`${os.tmpdir()}/${args.audio.name}.png`] });
},
};
async function ifExistDelete(path) {
if (fs.existsSync(path)) {
fs.rm(path, (err) => {
console.log('deleted');
if (err) {
return;
}
});
}
}

@ -1,59 +0,0 @@
/* TODO
*
* Merge with commands/fun/audio2image.js
*
*/
import { SlashCommandBuilder } from 'discord.js';
import fs from 'node:fs';
import os from 'node:os';
import fetch from 'node-fetch';
import util from 'node:util';
import stream from 'node:stream';
import utils from '../../utils/videos.js';
export default {
data: new SlashCommandBuilder()
.setName('image2audio')
.setDescription('Transform an image binary data into audio ( MIGHT BE VERY LOUD )')
.addAttachmentOption(option =>
option.setName('img')
.setDescription('The image that will become audio. Only tested with png and jpg.')
.setRequired(true)),
category: 'fun',
alias: ['i2a'],
async execute(interaction, args) {
if (!args.img) return interaction.reply('Please attach an image with your message.');
await interaction.deferReply();
ifExistDelete(`${os.tmpdir()}/${args.img.name}`);
ifExistDelete(`${os.tmpdir()}/1${args.img.name}`);
ifExistDelete(`${os.tmpdir()}/${args.img.name}.mp3`);
const streamPipeline = util.promisify(stream.pipeline);
const res = await fetch(args.img.url);
if (!res.ok) return interaction.editReply('An error has occured while trying to download your image.');
await streamPipeline(res.body, fs.createWriteStream(`${os.tmpdir()}/${args.img.name}`));
await utils.ffmpeg(['-i', `${os.tmpdir()}/${args.img.name}`, '-f', 'rawvideo', `${os.tmpdir()}/1${args.img.name}`]);
await utils.ffmpeg(['-sample_rate', '44100', '-ac', '1', '-f', 's16le', '-i', `${os.tmpdir()}/1${args.img.name}`, `${os.tmpdir()}/${args.img.name}.mp3`]);
const file = fs.statSync(`${os.tmpdir()}/${args.img.name}.mp3`);
const fileSize = (file.size / 1000000.0).toFixed(2);
if (fileSize > await utils.getMaxFileSize(interaction.guild)) return interaction.editReply('error');
interaction.editReply({ content: `Audio file is ${fileSize} MB` });
return interaction.followUp({ files: [`${os.tmpdir()}/${args.img.name}.mp3`] });
},
};
async function ifExistDelete(path) {
if (fs.existsSync(path)) {
fs.rm(path, (err) => {
console.log('deleted');
if (err) {
return;
}
});
}
}

@ -1,6 +1,6 @@
import { SlashCommandBuilder } from 'discord.js';
import { EmbedBuilder } from 'discord.js';
import { TwitterApi } from 'twitter-api-v2';
import Twit from 'twit';
import fetch from 'node-fetch';
import os from 'node:os';
import fs from 'node:fs';
@ -16,10 +16,10 @@ const Blacklists = db.Blacklists;
export default {
data: new SlashCommandBuilder()
.setName('tweet')
.setDescription('Send tweet from the bot twitter account. Please do not use it for advertisement and keep it english')
.setDescription('Send tweet from Haha yes twitter account. Please do not use it for advertisement and keep it english')
.addStringOption(option =>
option.setName('content')
.setDescription('!THIS IS NOT FEEDBACK! The content of the tweet you want to send me.')
.setDescription('The content of the tweet you want to send me.')
.setRequired(false))
.addAttachmentOption(option =>
option.setName('image')
@ -28,7 +28,6 @@ export default {
category: 'fun',
ratelimit: 3,
cooldown: 86400,
guildOnly: true,
async execute(interaction, args, client) {
const content = args.content;
const attachment = args.image;
@ -40,31 +39,13 @@ export default {
await interaction.deferReply({ ephemeral: false });
let tweet = content;
const date = new Date();
// If guild is less than 1 month old don't accept the tweet
if (interaction.guild.createdAt > date.setMonth(date.getMonth() - 1)) {
await interaction.editReply({ content: 'The server need to be 1 month old to be able to use this command!' });
return;
}
// Reset the date for the next check
date.setTime(Date.now());
// If the bot has been in the guild for less than 1 week don't accept the tweet.
if (interaction.guild.createdAt > date.setDate(date.getDate() - 7)) {
await interaction.editReply({ content: 'I need to be in this server for a week to be able to use this command!' });
}
// Reset the date for the next check
date.setTime(Date.now());
// If account is less than 6 months old don't accept the tweet ( alt prevention )
if (interaction.user.createdAt > date.setMonth(date.getMonth() - 6)) {
await interaction.editReply({ content: 'Your account is too new to be able to use this command!' });
return;
}
// Reset the date for the next check
// Reset the current date so it checks correctly for the 1 year requirement.
date.setTime(Date.now());
// If account is less than 1 year old don't accept attachment
@ -73,25 +54,13 @@ export default {
return;
}
// remove zero width space
if (tweet) {
// remove zero width space
tweet = tweet.replace('', '');
// This should only happen if someone tweets a zero width space
if (tweet.length === 0) {
return interaction.reply({ content: 'Uh oh! You are missing any content for me to tweet!', ephemeral: true });
}
wordToCensor.forEach(async word => {
if (tweet.toLowerCase().includes(word.toLowerCase())) {
const body = { type:'tweet', uid: interaction.user.id, reason: 'Automatic ban from banned word.' };
Blacklists.create(body);
}
await interaction.editReply({ content: 'Sike, you just posted cringe! Enjoy the blacklist :)' });
return;
}
});
if (tweet) {
// Detect banned word (Blacklist the user directly)
/* No worky (I don't remember what the fuck I wrote here)
if (wordToCensor.includes(tweet) || wordToCensor.includes(tweet.substring(0, tweet.length - 1)) || wordToCensor.includes(tweet.substring(1, tweet.length))) {
const body = { type:'tweet', uid: interaction.user.id, reason: 'Automatic ban from banned word.' };
Blacklists.create(body);
@ -99,7 +68,6 @@ export default {
await interaction.editReply({ content: 'Sike, you just posted cringe! Enjoy the blacklist :)' });
return;
}
*/
// Very simple link detection
if (new RegExp('([a-zA-Z0-9]+://)?([a-zA-Z0-9_]+:[a-zA-Z0-9_]+@)?([a-zA-Z0-9.-]+\\.[A-Za-z]{2,4})(:[0-9]+)?(/.*)?').test(tweet) && !tweet.includes('twitter.com')) {
@ -113,12 +81,11 @@ export default {
}
}
const userClient = new TwitterApi({
appKey: twiConsumer,
appSecret: twiConsumerSecret,
accessToken: twiToken,
accessSecret: twiTokenSecret,
const T = new Twit({
consumer_key: twiConsumer,
consumer_secret: twiConsumerSecret,
access_token: twiToken,
access_token_secret: twiTokenSecret,
});
try {
@ -140,8 +107,17 @@ export default {
return interaction.editReply({ content: 'Gifs can\'t be larger than 15 MB!' });
}
const image = await userClient.v1.uploadMedia(`${os.tmpdir()}/${attachment.name}`);
Tweet(image);
const b64Image = fs.readFileSync(`${os.tmpdir()}/${attachment.name}`, { encoding: 'base64' });
T.post('media/upload', { media_data: b64Image }, function(err, data) {
if (err) {
console.log('OH NO AN ERROR!!!!!!!');
console.error(err);
return interaction.editReply({ content: 'OH NO!!! AN ERROR HAS occurred!!! please hold on while i find what\'s causing this issue! ' });
}
else {
Tweet(data);
}
});
}
else {
await interaction.editReply({ content: 'File type not supported, you can only send jpg/png/gif' });
@ -158,47 +134,74 @@ export default {
return;
}
async function Tweet(img) {
let options = null;
if (img) {
options = { media: { media_ids: new Array(img) } };
}
const tweeted = await userClient.v2.tweet(tweet, options);
const tweetid = tweeted.data.id;
const FunnyWords = ['oppaGangnamStyle', '69', '420', 'cum', 'funnyMan', 'GUCCISmartToilet', 'TwitterForClowns', 'fart', 'ok', 'hi', 'howAreYou', 'WhatsNinePlusTen', '21'];
const TweetLink = `https://vxtwitter.com/${FunnyWords[Math.floor((Math.random() * FunnyWords.length))]}/status/${tweetid}`;
let channel = await client.channels.resolve(twiChannel);
channel.send(TweetLink);
const Embed = new EmbedBuilder()
.setAuthor({ name: interaction.user.username, iconURL: interaction.user.displayAvatarURL() })
.setDescription(tweet ? tweet : 'No content.')
.addFields(
{ name: 'Link', value: TweetLink, inline: true },
{ name: 'Tweet ID', value: tweetid, inline: true },
{ name: 'Channel ID', value: interaction.channel.id, inline: true },
{ name: 'Message ID', value: interaction.id, inline: true },
{ name: 'Author', value: `${interaction.user.username} (${interaction.user.id})`, inline: true },
)
.setTimestamp();
if (interaction.guild) {
Embed.addFields(
{ name: 'Guild', value: `${interaction.guild.name} (${interaction.guild.id})`, inline: true },
{ name: 'message link', value: `https://discord.com/channels/${interaction.guild.id}/${interaction.channel.id}/${interaction.id}`, inline: true },
);
function Tweet(data) {
let options = {
status: tweet,
};
if (data && tweet) {
options = {
status: tweet,
media_ids: new Array(data.media_id_string),
};
}
else {
Embed.addFields({ name: 'message link', value: `https://discord.com/channels/@me/${interaction.channel.id}/${interaction.id}` });
else if (data) {
options = {
media_ids: new Array(data.media_id_string),
};
}
if (attachment) Embed.setImage(attachment.url);
T.post('statuses/update', options, function(err, response) {
if (err) {
// Rate limit exceeded
if (err.code == 88) return interaction.editReply({ content: err.interaction });
// Tweet needs to be a bit shorter.
if (err.code == 186) return interaction.editReply({ content: `${err.interaction} Your interaction was ${tweet.length} characters, you need to remove ${tweet.length - 280} characters (This count may be inaccurate if your interaction contained link)` });
// Status is a duplicate.
if (err.code == 187) return interaction.editReply({ content: err.interaction });
// To protect our users from spam and other malicious activity, this account is temporarily locked.
if (err.code == 326) return interaction.editReply({ content: err.interaction });
console.error('OH NO!!!!');
console.error(err);
return interaction.editReply({ content: 'OH NO!!! AN ERROR HAS occurred!!! please hold on while i find what\'s causing this issue!' });
}
channel = await client.channels.resolve(twiLogChannel);
channel.send({ embeds: [Embed] });
return interaction.editReply({ content: `Go see ur epic tweet ${TweetLink}` });
const tweetid = response.id_str;
const FunnyWords = ['oppaGangnamStyle', '69', '420', 'cum', 'funnyMan', 'GUCCISmartToilet', 'TwitterForClowns', 'fart', 'ok', 'hi', 'howAreYou', 'WhatsNinePlusTen', '21'];
const TweetLink = `https://twitter.com/${FunnyWords[Math.floor((Math.random() * FunnyWords.length))]}/status/${tweetid}`;
// Im too lazy for now to make an entry in config.json
let channel = client.channels.resolve(twiChannel);
channel.send(TweetLink);
const Embed = new EmbedBuilder()
.setAuthor({ name: interaction.user.username, iconURL: interaction.user.displayAvatarURL() })
.setDescription(tweet ? tweet : 'No content.')
.addFields(
{ name: 'Link', value: TweetLink, inline: true },
{ name: 'Tweet ID', value: tweetid, inline: true },
{ name: 'Channel ID', value: interaction.channel.id, inline: true },
{ name: 'Message ID', value: interaction.id, inline: true },
{ name: 'Author', value: `${interaction.user.username} (${interaction.user.id})`, inline: true },
)
.setTimestamp();
if (interaction.guild) {
Embed.addFields(
{ name: 'Guild', value: `${interaction.guild.name} (${interaction.guild.id})`, inline: true },
{ name: 'message link', value: `https://discord.com/channels/${interaction.guild.id}/${interaction.channel.id}/${interaction.id}`, inline: true },
);
}
else {
Embed.addFields({ name: 'message link', value: `https://discord.com/channels/@me/${interaction.channel.id}/${interaction.id}` });
}
if (attachment) Embed.setImage(attachment.url);
channel = client.channels.resolve(twiLogChannel);
channel.send({ embeds: [Embed] });
return interaction.editReply({ content: `Go see ur epic tweet ${TweetLink}` });
});
}
},
};

@ -12,9 +12,6 @@ export default {
.setDescription('Force the generation of the video in non-nsfw channel.')
.setRequired(false)),
category: 'fun',
ratelimit: 2,
cooldown: 60,
parallelLimit: 30,
async execute(interaction, args) {
if (!interaction.channel.nsfw && !args.force) return interaction.reply(`Please execute this command in an NSFW channel ( Content might not be NSFW but since the video are user submitted better safe than sorry ) OR do \`\`${interaction.prefix}ytp --force\`\` to make the command work outside of nsfw channel BE AWARE THAT IT WON'T CHANGE THE FINAL RESULT SO NSFW CAN STILL HAPPEN`);
@ -72,19 +69,19 @@ export default {
},
};
await new YTPGenerator().configurateAndGo(options)
new YTPGenerator().configurateAndGo(options)
.then(() => {
loadingmsg.delete();
return interaction.followUp({ content: 'Here is your YTP! Remember, it might contain nsfw, so be careful!', files: [`${os.tmpdir()}/${interaction.id}_YTP.mp4`] })
return interaction.reply({ content: 'Here is your YTP! Remember, it might contain nsfw, so be careful!', files: [`${os.tmpdir()}/${interaction.id}_YTP.mp4`] })
.catch(err => {
console.error(err);
return interaction.followUp({ files: [`./asset/ytp/error${Math.floor(Math.random() * 2) + 1}.mp4`] });
return interaction.reply('Whoops, look like the vid might be too big for discord, my bad, please try again');
});
})
.catch(err => {
console.error(err);
loadingmsg.delete();
return interaction.followUp({ files: [`./asset/ytp/error${Math.floor(Math.random() * 2) + 1}.mp4`] });
return interaction.reply({ files: [`./asset/ytp/error${Math.floor(Math.random() * 2) + 1}.mp4`] });
});
},
};

@ -43,7 +43,7 @@ export default {
.setTimestamp();
user.send({ embeds: [Embed] });
return interaction.reply({ content: `DM sent to ${user.username} (${user.id})` });
return interaction.reply({ content: `DM sent to ${user.username}`, ephemeral: true });
/*
const Attachment = (message.attachments).array();
if (Attachment[0]) {
@ -58,10 +58,10 @@ export default {
else {
client.users.resolve(user).send(Embed)
.then(() => {
return interaction.reply(`DM sent to ${user.username}`);
return interaction.reply(`DM sent to ${user.tag}`);
})
.catch(() => {
return interaction.reply(`Could not send a DM to ${user.username}`);
return interaction.reply(`Could not send a DM to ${user.tag}`);
});
}
*/

@ -1,30 +0,0 @@
import { SlashCommandBuilder } from 'discord.js';
import util from 'node:util';
import stream from 'node:stream';
import fs from 'node:fs';
export default {
data: new SlashCommandBuilder()
.setName('downloadandload')
.setDescription('Download a command and load it.')
.addAttachmentOption(option =>
option.setName('file')
.setDescription('The .js file that will be loaded by the bot.')
.setRequired(true)),
category: 'owner',
ownerOnly: true,
async execute(interaction, args, client) {
await interaction.deferReply();
const streamPipeline = util.promisify(stream.pipeline);
const res = await fetch(args.file.url);
if (!res.ok) return interaction.editReply('An error has occured while trying to download the command.');
await streamPipeline(res.body, fs.createWriteStream(`./tmp/${args.file.name}`));
let command = await import(`../../tmp/${args.file.name}`);
command = command.default;
client.commands.set(command.data.name, command);
return await interaction.editReply(`${command.data.name} has been loaded.`);
},
};

@ -34,46 +34,40 @@ export default {
Blacklists.create(body);
let user = userid;
await client.users.fetch(userid);
user = client.users.resolve(userid).username;
user = client.users.resolve(userid).tag;
return interaction.editReply(`${user} (${userid}) has been blacklisted from ${command} with the following reason \`${reason}\``);
return interaction.editReply(`${user} has been blacklisted from ${command} with the following reason \`${reason}\``);
}
else {
const row = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(`yes${interaction.user.id}${interaction.id}`)
.setCustomId(`yes${interaction.user.id}`)
.setLabel('Yes')
.setStyle(ButtonStyle.Primary),
)
.addComponents(
new ButtonBuilder()
.setCustomId(`no${interaction.user.id}${interaction.id}`)
.setCustomId(`no${interaction.user.id}`)
.setLabel('No')
.setStyle(ButtonStyle.Danger),
);
await interaction.editReply({ content: 'This user is already blacklisted, do you want to unblacklist him?', ephemeral: true, components: [row] });
return listenButton(client, interaction, command, userid, interaction.user);
interaction.client.once('interactionCreate', async (interactionMenu) => {
if (interaction.user !== interactionMenu.user) return;
if (!interactionMenu.isButton) return;
interactionMenu.update({ components: [] });
if (interactionMenu.customId === `yes${interaction.user.id}`) {
Blacklists.destroy({ where: { type:command, uid:userid } });
return interaction.editReply(`The following ID have been unblacklisted from ${command}: ${userid}`);
}
else {
return interaction.editReply('No one has been unblacklisted.');
}
});
}
},
};
async function listenButton(client, interaction, command, userid, user = interaction.user, originalId = interaction.id) {
client.once('interactionCreate', async (interactionMenu) => {
if (user !== interactionMenu.user) return listenButton(client, interaction, command, userid, user, originalId);
if (!interactionMenu.isButton()) return;
await interactionMenu.update({ components: [] });
if (interactionMenu.customId === `yes${interaction.user.id}${originalId}`) {
Blacklists.destroy({ where: { type:command, uid:userid } });
return interaction.editReply(`The following ID have been unblacklisted from ${command}: ${userid}`);
}
else {
return interaction.editReply('No one has been unblacklisted.');
}
});
}

@ -1,6 +1,6 @@
import { SlashCommandBuilder } from 'discord.js';
import { EmbedBuilder } from 'discord.js';
import { execFile } from 'node:child_process';
import { exec } from 'node:child_process';
import db from '../../models/index.js';
const donator = db.donator;
@ -15,7 +15,7 @@ export default {
const Donator = await donator.findAll({ order: ['id'] });
const client = interaction.client;
const tina = await client.users.fetch('336492042299637771');
const creator = await client.users.fetch('267065637183029248');
const owner = await client.users.fetch('267065637183029248');
const maintainer = await client.users.fetch(ownerId);
let description = 'I\'m a fun multipurpose bot made using [discord.js](https://github.com/discordjs/discord.js)'
@ -25,7 +25,7 @@ export default {
for (let i = 0; i < Donator.length; i++) {
const user = await client.users.fetch(Donator[i].get('userID').toString());
if (user !== null) {
description += `**${user.username} (${user.id}) | ${Donator[i].get('comment')}**\n`;
description += `**${user.tag} (${user.id}) | ${Donator[i].get('comment')}**\n`;
}
else {
description += `**A user of discord (${user.id}) | ${Donator[i].get('comment')} (This user no longer share a server with the bot)**\n`;
@ -36,26 +36,26 @@ export default {
description += 'No one :(\n';
}
description += `\nThanks to ${tina.username} (336492042299637771) for inspiring me for making this bot!`;
description += `\nThanks to ${tina.tag} (336492042299637771) for inspiring me for making this bot!`;
// description += '\nThanks to Jetbrains for providing their IDE!';
execFile('git', ['rev-parse', '--short', 'HEAD'], (err, stdout) => {
exec('git rev-parse --short HEAD', (err, stdout) => {
const aboutEmbed = new EmbedBuilder()
.setColor(interaction.member ? interaction.member.displayHexColor : 'Navy')
.setAuthor({ name: client.user.username, iconURL: client.user.displayAvatarURL(), url: 'https://libtar.de' })
.setAuthor({ name: client.user.tag, iconURL: client.user.displayAvatarURL(), url: 'https://libtar.de' })
.setTitle('About me')
.setDescription(description)
.addFields(
{ name: 'Current commit', value: stdout },
{ name: 'Current maintainer', value: `${maintainer.username} (${ownerId})` },
{ name: 'Current maintainer', value: `${maintainer.tag} (${ownerId})` },
{ name: 'Gitea (Main)', value: 'https://git.namejeff.xyz/Supositware/Haha-Yes', inline: true },
{ name: 'Github (Mirror)', value: 'https://github.com/Supositware/Haha-yes', inline: true },
{ name: 'Privacy Policy', value: 'https://libtar.de/discordprivacy.txt', inline: true },
{ name: 'Status page', value: uptimePage.toString(), inline: true },
)
.setFooter({ text: `Original bot made by ${creator.username} (267065637183029248)` });
.setFooter({ text: `Original bot made by ${owner.tag} (267065637183029248)` });
interaction.reply({ embeds: [aboutEmbed] });
});

@ -18,11 +18,6 @@ export default {
cooldown: 86400,
async execute(interaction, args) {
const url = args.url;
// This is rather rudementary, a proper way would be using yt-dlp to know if it is a playlist
if (url.includes('list=')) {
return interaction.reply({ content: '❌ Playlists are not allowed!', ephemeral: true });
}
if (!await utils.stringIsAValidurl(url)) {
console.error(`Not a url!!! ${url}`);
return interaction.reply({ content: '❌ This does not look like a valid url!', ephemeral: true });
@ -30,7 +25,7 @@ export default {
await interaction.deferReply({ ephemeral: true });
utils.downloadVideo(url, interaction.id, 'bestvideo[height<=?480]+bestaudio/best')
utils.downloadVideo(url, interaction.id, 'mp4')
.then(async () => {
const file = fs.readdirSync(os.tmpdir()).filter(fn => fn.startsWith(interaction.id));
const output = `${os.tmpdir()}/${file}`;

@ -17,7 +17,7 @@ export default {
if (Donator[0]) {
for (let i = 0; i < Donator.length; i++) {
const user = await client.users.fetch(Donator[i].get('userID').toString());
if (user !== null) {donatorMessage += `**${user.username} (${user.id}) | ${Donator[i].get('comment')}**\n`;}
if (user !== null) {donatorMessage += `**${user.tag} (${user.id}) | ${Donator[i].get('comment')}**\n`;}
else {donatorMessage += `**A user of discord (${user.id}) | ${Donator[i].get('comment')} (This user no longer share a server with the bot)**\n`;}
}

@ -1,15 +1,11 @@
import { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, StringSelectMenuBuilder } from 'discord.js';
import { execFile } from 'node:child_process';
import { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, SelectMenuBuilder } from 'discord.js';
import { exec } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import utils from '../../utils/videos.js';
let client;
let maxFileSize;
let { ytdlpMaxResolution } = process.env;
// Convert to number as process.env is always a string
ytdlpMaxResolution = Number(ytdlpMaxResolution);
let cleanUp;
export default {
data: new SlashCommandBuilder()
@ -26,10 +22,6 @@ export default {
.addBooleanOption(option =>
option.setName('compress')
.setDescription('Compress the video?')
.setRequired(false))
.addBooleanOption(option =>
option.setName('description')
.setDescription('Include the video description?')
.setRequired(false)),
category: 'utility',
alias: ['dl'],
@ -38,8 +30,10 @@ export default {
client = c;
const url = args.url;
const format = args.format;
maxFileSize = await utils.getMaxFileSize(interaction.guild);
interaction.doCompress = args.compress;
if (interaction.cleanUp) {
cleanUp = interaction.cleanUp;
}
await interaction.deferReply({ ephemeral: false });
@ -54,7 +48,7 @@ export default {
if (format) {
let qualitys = await new Promise((resolve, reject) => {
execFile('./bin/yt-dlp', [url, '--print', '%()j'], (err, stdout, stderr) => {
exec(`./bin/yt-dlp "${url}" --print "%()j"`, (err, stdout, stderr) => {
if (err) {
reject(stderr);
}
@ -64,8 +58,8 @@ export default {
resolve(stdout);
});
});
qualitys = JSON.parse(qualitys);
const options = [];
qualitys.formats.forEach(f => {
@ -95,8 +89,8 @@ export default {
const row = new ActionRowBuilder()
.addComponents(
new StringSelectMenuBuilder()
.setCustomId(`downloadQuality${interaction.user.id}${interaction.id}`)
new SelectMenuBuilder()
.setCustomId(`downloadQuality${interaction.user.id}`)
.setPlaceholder('Nothing selected')
.setMinValues(1)
.setMaxValues(2)
@ -109,31 +103,25 @@ export default {
client.on('interactionCreate', async (interactionMenu) => {
if (interaction.user !== interactionMenu.user) return;
if (!interactionMenu.isSelectMenu()) return;
if (interactionMenu.customId === `downloadQuality${interaction.user.id}${interaction.id}`) {
if (interactionMenu.customId === `downloadQuality${interaction.user.id}`) {
await interactionMenu.deferReply({ ephemeral: false });
await checkSize(url, interactionMenu.values[0], args, interaction);
return download(url, interactionMenu, interaction, undefined, true);
download(url, interactionMenu, interaction);
}
});
return;
}
const newFormat = await checkSize(url, undefined, args, interaction);
return download(url, interaction, interaction, newFormat, args.description);
download(url, interaction);
},
};
async function download(url, interaction, originalInteraction, format = undefined, description = false) {
async function download(url, interaction, originalInteraction) {
let format = 'bestvideo*+bestaudio/best';
const Embed = new EmbedBuilder()
.setColor(interaction.member ? interaction.member.displayHexColor : 'Navy')
.setAuthor({ name: `Downloaded by ${interaction.user.username}`, iconURL: interaction.user.displayAvatarURL(), url: url })
.setFooter({ text: `You can get the original video by clicking on the "Downloaded by ${interaction.user.username}" message!` });
if (description) {
Embed.setDescription(await getVideoDescription(url));
}
.setAuthor({ name: `Downloaded by ${interaction.user.tag}`, iconURL: interaction.user.displayAvatarURL(), url: url })
.setFooter({ text: `You can get the original video by clicking on the "Downloaded by ${interaction.user.tag}" message!` });
if (interaction.customId === `downloadQuality${interaction.user.id}${originalInteraction.id}` && !format) {
if (interaction.customId === `downloadQuality${interaction.user.id}`) {
format = interaction.values[0];
if (interaction.values[1]) format += '+' + interaction.values[1];
}
@ -143,6 +131,8 @@ async function download(url, interaction, originalInteraction, format = undefine
const file = fs.readdirSync(os.tmpdir()).filter(fn => fn.startsWith(interaction.id));
let output = `${os.tmpdir()}/${file}`;
const fileStat = fs.statSync(output);
const fileSize = fileStat.size / 1000000.0;
const compressInteraction = originalInteraction ? originalInteraction : interaction;
if (compressInteraction.doCompress) {
const presets = [ 'Social 8 MB 3 Minutes 360p30', 'Social 50 MB 10 Minutes 480p30', 'Social 50 MB 5 Minutes 720p30', 'Social 100 MB 5 Minutes 1080p30' ];
@ -157,8 +147,8 @@ async function download(url, interaction, originalInteraction, format = undefine
const row = new ActionRowBuilder()
.addComponents(
new StringSelectMenuBuilder()
.setCustomId(`preset${interaction.user.id}${interaction.id}`)
new SelectMenuBuilder()
.setCustomId(`preset${interaction.user.id}`)
.setPlaceholder('Nothing selected')
.addOptions(options),
);
@ -168,13 +158,10 @@ async function download(url, interaction, originalInteraction, format = undefine
client.on('interactionCreate', async (interactionMenu) => {
if (interaction.user !== interactionMenu.user) return;
if (!interactionMenu.isSelectMenu()) return;
if (interactionMenu.customId === `preset${interaction.user.id}${interaction.id}`) {
if (interactionMenu.customId === `preset${interaction.user.id}`) {
await interactionMenu.deferReply({ ephemeral: false });
compress(file, interactionMenu, Embed);
if (interaction.isMessage) {
interaction.deleteReply();
interaction.cleanUp();
}
if (interaction.isMessage) cleanUp();
}
});
return;
@ -185,51 +172,28 @@ async function download(url, interaction, originalInteraction, format = undefine
const codec = await utils.getVideoCodec(output);
if (bannedFormats.includes(codec)) {
console.log('Reencoding video');
const oldOutput = output;
output = `${os.tmpdir()}/264${file}`;
await utils.ffmpeg(['-i', oldOutput, '-vcodec', 'libx264', '-acodec', 'aac', output]);
}
const fileStat = fs.statSync(output);
const fileSize = fileStat.size / 1000000.0;
Embed.setAuthor({ name: `${Embed.data.author.name} (${fileSize.toFixed(2)} MB)`, iconURL: Embed.data.author.icon_url, url: Embed.data.author.url });
let message = null;
if (interaction.isMessage && interaction.reference !== null) {
const channel = client.channels.resolve(interaction.reference.channelId);
message = await channel.messages.fetch(interaction.reference.messageId);
await utils.ffmpeg(`-i ${oldOutput} -vcodec libx264 -acodec aac ${output}`);
}
if (fileSize > 100) {
await interaction.deleteReply();
await interaction.followUp('Uh oh! The video you tried to download is too big!', { ephemeral: true });
}
else if (fileSize > maxFileSize) {
else if (fileSize > 8) {
const fileurl = await utils.upload(output)
.catch(err => {
console.error(err);
});
await interaction.editReply({ content: `File was bigger than ${maxFileSize} mb. It has been uploaded to an external site.`, embeds: [Embed], ephemeral: false });
if (interaction.isMessage && message) {
await message.reply({ content: fileurl });
}
else {
await interaction.followUp({ content: fileurl, ephemeral: false });
}
}
else if (interaction.isMessage && message) {
await message.reply({ embeds: [Embed], files: [output] });
await interaction.editReply({ content: 'File was bigger than 8 mb. It has been uploaded to an external site.', embeds: [Embed], ephemeral: false });
await interaction.followUp({ content: fileurl, ephemeral: false });
}
else {
await interaction.editReply({ embeds: [Embed], files: [output], ephemeral: false });
}
if (interaction.isMessage) {
interaction.deleteReply();
interaction.cleanUp();
}
if (interaction.isMessage) cleanUp();
})
.catch(async err => {
console.error(err);
@ -242,63 +206,17 @@ async function download(url, interaction, originalInteraction, format = undefine
async function compress(input, interaction, embed) {
const output = `compressed${input}.mp4`;
// Delete the file as it apparently don't overwrite?
if (fs.existsSync(output)) {
fs.rmSync(output);
}
fs.rmSync(output);
utils.compressVideo(`${os.tmpdir()}/${input}`, output, interaction.values[0])
.then(async () => {
const fileStat = fs.statSync(`${os.tmpdir()}/${output}`);
const fileSize = fileStat.size / 1000000.0;
embed.setAuthor({ name: `${embed.data.author.name} (${fileSize.toFixed(2)} MB)`, iconURL: embed.data.author.icon_url, url: embed.data.author.url });
if (fileSize > maxFileSize) {
await interaction.editReply({ content: `File was bigger than ${maxFileSize} mb. It has been uploaded to an external site.`, ephemeral: false });
if (fileSize > 8) {
await interaction.editReply({ content: 'File was bigger than 8 mb. but due to the compression it is not being uploaded externally.', ephemeral: true });
}
else {
await interaction.editReply({ embeds: [embed], files: [`${os.tmpdir()}/${output}`], ephemeral: false });
}
});
}
async function checkSize(url, format, args, interaction, tries = 0) {
const resolutions = [144, 240, 360, 480, 720, 1080, 1440, 2160];
while (tries < 4) {
format = `bestvideo[height<=?${resolutions[resolutions.indexOf(ytdlpMaxResolution) - tries]}]+bestaudio/best`;
const aproxFileSize = await utils.getVideoSize(url, format);
if (isNaN(aproxFileSize)) return format;
if (format || tries >= 4) {
if (aproxFileSize > 100 && !args.compress && tries > 4) {
return await interaction.followUp(`Uh oh! The video you tried to download is larger than 100 mb (is ${aproxFileSize} mb)! Try again with a lower resolution format.`);
}
else if (aproxFileSize > 500 && tries > 4) {
return await interaction.followUp(`Uh oh! The video you tried to download is larger than 500 mb (is ${aproxFileSize} mb)! Try again with a lower resolution format.`);
}
}
if (aproxFileSize < 100) {
return format;
}
if (tries < 4 && aproxFileSize > 100) {
tries++;
}
}
}
async function getVideoDescription(urlArg) {
return await new Promise((resolve, reject) => {
execFile('./bin/yt-dlp', [urlArg, '--no-warnings', '-O', '%(description)s'], (err, stdout, stderr) => {
if (err) {
reject(stderr);
}
if (stderr) {
console.error(stderr);
}
resolve(stdout.slice(0, 240));
});
});
}

@ -18,7 +18,7 @@ export default {
category: 'utility',
async execute(interaction, args) {
const Embed = new EmbedBuilder()
.setAuthor({ name: `${interaction.user.username} (${interaction.user.id})`, iconURL: interaction.user.avatarURL() })
.setAuthor({ name: `${interaction.user.tag} (${interaction.user.id})`, iconURL: interaction.user.avatarURL() })
.setTimestamp();
if (interaction.guild) Embed.addFields({ name: 'Guild', value: `${interaction.guild.name} (${interaction.guild.id})`, inline: true });

@ -1,6 +1,5 @@
import { SlashCommandBuilder, EmbedBuilder, AttachmentBuilder, PermissionsBitField } from 'discord.js';
import fs from 'node:fs';
import ratelimiter from '../../utils/ratelimiter.js';
const { ownerId, prefix } = process.env;
const prefixs = prefix.split(',');
@ -114,13 +113,6 @@ export default {
embed.addFields({ name: 'Bot permission', value: `\`${perm.join('` `')}\``, inline: true });
}
if (command.parallelLimit) {
const paralellimit = ratelimiter.checkParallel(interaction.user, command.data.name, command);
embed.addFields({ name: 'Current number of executions', value: `\`${paralellimit.current}\``, inline: false });
embed.addFields({ name: 'Maximum number of executions', value: `\`${command.parallelLimit}\``, inline: true });
}
if (fs.existsSync(`./asset/img/command/${command.category}/${command.data.name}.png`)) {
const file = new AttachmentBuilder(`./asset/img/command/${command.category}/${command.data.name}.png`);
embed.setImage(`attachment://${command.data.name}.png`);

@ -12,55 +12,36 @@ export default {
if (!isOptOut) {
const body = { userID: interaction.user.id };
await db.optout.create(body);
await interaction.reply({ content: 'You have successfully been opt out.', ephemeral: true });
return await interaction.reply({ content: 'You have successfully been opt out.' });
}
else {
const row = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(`yes${interaction.user.id}${interaction.id}`)
.setLabel('Yes')
.setStyle(ButtonStyle.Primary),
)
.addComponents(
new ButtonBuilder()
.setCustomId(`no${interaction.user.id}${interaction.id}`)
.setLabel('No')
.setStyle(ButtonStyle.Danger),
);
await interaction.reply({ content: 'You are already opt out, do you wish to opt in?', components: [row], ephemeral: true });
listenButton(client, interaction, interaction.user);
}
return interaction.followUp({
content:
'As a reminder here what opting out does:\n'
+ '- Your user ID will no longer be used for debug logging.\n'
+ '- servers will no longer be shown in added/kicked stats.\n'
+ '- Your messages won\'t be quoted.\n'
+ '- Won\'t show the arguments from commands.',
ephemeral: true,
},
);
const row = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(`yes${interaction.user.id}`)
.setLabel('Yes')
.setStyle(ButtonStyle.Primary),
)
.addComponents(
new ButtonBuilder()
.setCustomId(`no${interaction.user.id}`)
.setLabel('No')
.setStyle(ButtonStyle.Danger),
);
await interaction.reply({ content: 'You are already opt out, do you wish to opt in?', components: [row] });
client.on('interactionCreate', async (interactionMenu) => {
if (interaction.user !== interactionMenu.user) return;
if (!interactionMenu.isButton) return;
interactionMenu.update({ components: [] });
if (interactionMenu.customId === `yes${interaction.user.id}`) {
await db.optout.destroy({ where: { userID: interaction.user.id } });
return interaction.editReply('You have successfully been opt in');
}
else {
return interaction.editReply('Nothing has been changed.');
}
});
},
};
async function listenButton(client, interaction, user = interaction.user, originalId = interaction.id) {
client.once('interactionCreate', async (interactionMenu) => {
if (user !== interactionMenu.user) return listenButton(client, interaction, user, originalId);
if (!interactionMenu.isButton()) return;
await interactionMenu.update({ components: [] });
if (interactionMenu.customId === `yes${interaction.user.id}${originalId}`) {
db.optout.destroy({ where: { userID: interaction.user.id } });
return interaction.editReply({ content: 'You have successfully been opt in', ephemeral: true });
}
else {
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
}
});
}

@ -36,7 +36,7 @@ export default {
const statsEmbed = new EmbedBuilder()
.setColor(interaction.member ? interaction.member.displayHexColor : 'Navy')
.setTitle('Bot stats')
.setAuthor({ name: client.user.username, iconURL: client.user.displayAvatarURL(), url: 'https://libtar.de' })
.setAuthor({ name: client.user.tag, iconURL: client.user.displayAvatarURL(), url: 'https://libtar.de' })
.addFields(
{ name: 'Servers', value: client.guilds.cache.size.toString(), inline: true },
{ name: 'Channels', value: client.channels.cache.size.toString(), inline: true },

@ -20,7 +20,7 @@ export default {
}
const Embed = new EmbedBuilder()
.setColor(member ? member.displayHexColor : 'Navy')
.setAuthor({ name: `${user.username} (${user.id})`, iconURL: user.displayAvatarURL() })
.setAuthor({ name: `${user.tag} (${user.id})`, iconURL: user.displayAvatarURL() })
.addFields(
{ name: 'Current rank hex color', value: member ? member.displayHexColor : 'No rank color', inline: true },
{ name: 'Joined guild at', value: member ? member.joinedAt.toString() : 'Not in this guild', inline: true },

@ -3,7 +3,7 @@ import utils from '../../utils/videos.js';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { execFile } from 'node:child_process';
import { exec } from 'node:child_process';
const { NODE_ENV } = process.env;
@ -14,28 +14,11 @@ export default {
.addStringOption(option =>
option.setName('url')
.setDescription('URL of the video you want to convert')
.setRequired(true))
.addIntegerOption(option =>
option.setName('quality')
.setDescription('Quality of the gif conversion. Default 70. Number between 1 and 100')
.setRequired(false))
.addBooleanOption(option =>
option.setName('noloop')
.setDescription('Stop the gif from looping')
.setRequired(false)),
.setRequired(true)),
category: 'utility',
alias: ['v2g'],
async execute(interaction, args) {
await interaction.deferReply({ ephemeral: false });
const maxFileSize = await utils.getMaxFileSize(interaction.guild);
const url = args.url;
let quality = args.quality;
if (quality <= 0) {
quality = 1;
}
else if (quality > 100) {
quality = 100;
}
if (!await utils.stringIsAValidurl(url)) {
console.error(`Not a url!!! ${url}`);
@ -50,11 +33,11 @@ export default {
const gifsicleOutput = output.replace(path.extname(output), 'gifsicle.gif');
// Extract every frame for gifski
await utils.ffmpeg(['-i', output, `${os.tmpdir()}/frame${interaction.id}%04d.png`]);
await utils.ffmpeg(`-i ${output} ${os.tmpdir()}/frame${interaction.id}%04d.png`);
// Make it look better
await gifski(gifskiOutput, `${os.tmpdir()}/frame${interaction.id}*`, quality);
await gifski(gifskiOutput, `${os.tmpdir()}/frame${interaction.id}*`);
// Optimize it
await gifsicle(gifskiOutput, gifsicleOutput, args.noloop);
await gifsicle(gifskiOutput, gifsicleOutput);
const fileStat = fs.statSync(gifsicleOutput);
const fileSize = fileStat.size / 1000000.0;
@ -63,12 +46,12 @@ export default {
await interaction.deleteReply();
await interaction.followUp('❌ Uh oh! The video once converted is too big!', { ephemeral: true });
}
else if (fileSize > maxFileSize) {
else if (fileSize > 8) {
const fileURL = await utils.upload(gifsicleOutput)
.catch(err => {
console.error(err);
});
await interaction.editReply({ content: ` File was bigger than ${maxFileSize} mb. It has been uploaded to an external site.\n${fileURL}`, ephemeral: false });
await interaction.editReply({ content: ` File was bigger than 8 mb. It has been uploaded to an external site.\n${fileURL}`, ephemeral: false });
}
else {
await interaction.editReply({ files: [gifsicleOutput], ephemeral: false });
@ -77,10 +60,9 @@ export default {
},
};
async function gifski(output, input, quality) {
async function gifski(output, input) {
return await new Promise((resolve, reject) => {
// Shell: true should be fine as no user input is being passed
execFile('gifski', ['--quality', quality ? quality : 70, '-o', output, input], { shell: true }, (err, stdout, stderr) => {
exec(`gifski --quality 70 -o ${output} ${input}`, (err, stdout, stderr) => {
if (err) {
reject(stderr);
}
@ -93,10 +75,9 @@ async function gifski(output, input, quality) {
});
}
async function gifsicle(input, output, loop = false) {
async function gifsicle(input, output) {
return await new Promise((resolve, reject) => {
// Shell: true should be fine as no user input is being passed
execFile('gifsicle', ['--colors', '256', loop ? '--no-loopcount' : '', '-i', input, '-o', output], { shell: true }, (err, stdout, stderr) => {
exec(`gifsicle --colors 256 -i ${input} -o ${output}`, (err, stdout, stderr) => {
if (err) {
reject(stderr);
}

@ -1,5 +1,3 @@
// TODO: Moving that to a dedicated function that works for both messages and interactions
import { PermissionFlagsBits, InteractionType } from 'discord.js';
import db from '../../models/index.js';
import ratelimiter from '../../utils/ratelimiter.js';
@ -13,22 +11,15 @@ export default {
if (interaction.type !== InteractionType.ApplicationCommand) return;
const globalBlacklist = await db.Blacklists.findOne({ where: { type:'global', uid:interaction.user.id } });
// const serverBlacklist = await db.Blacklists.findOne({ where: { type:'guild', uid:interaction.guild.id } });
const commandBlacklist = await db.Blacklists.findOne({ where: { type:interaction.commandName, uid:interaction.user.id } });
if (globalBlacklist) {
return interaction.reply({ content: `You are globally blacklisted for the following reason: \`${globalBlacklist.reason}\``, ephemeral: true });
}
/* Server blacklist is untested
else if (serverBlacklist) {
return interaction.reply({ content: `This guild has been blacklisted for the following reason: \`${serverBlacklist.reason}\``, ephemeral: true });
}
*/
else if (commandBlacklist) {
return interaction.reply({ content: `You are blacklisted for the following reason: \`${commandBlacklist.reason}\``, ephemeral: true });
}
const userTag = interaction.user.username;
const userTag = interaction.user.tag;
const userID = interaction.user.id;
const commandName = interaction.commandName;
@ -38,8 +29,12 @@ export default {
const isOptOut = await db.optout.findOne({ where: { userID: interaction.user.id } });
const timestamp = new Date();
console.log(`[${timestamp.toISOString()}] \x1b[33m${ isOptOut ? 'A user' : `${userTag} (${userID})`}\x1b[0m launched command \x1b[33m${commandName}\x1b[0m using slash`);
if (isOptOut) {
console.log(`A user launched command \x1b[33m${commandName}\x1b[0m with slash`);
}
else {
console.log(`\x1b[33m${userTag} (${userID})\x1b[0m launched command \x1b[33m${commandName}\x1b[0m with slash`);
}
// Owner only check
@ -47,11 +42,6 @@ export default {
return interaction.reply({ content: '❌ This command is reserved for the owner!', ephemeral: true });
}
// Guild only check
if (command.guildOnly && !interaction.guild) {
return interaction.reply({ content: '❌ This command only work in a server!', ephemeral: true });
}
// Check if the bot has the needed permissions
if (command.default_permission) {
const clientMember = await interaction.guild.members.fetch(client.user.id);
@ -69,18 +59,8 @@ export default {
}
*/
// Check if the limit of parallel execution has been reached
if (command.parallelLimit) {
const doParallelLimit = await ratelimiter.checkParallel(interaction.user, commandName, command);
if (doParallelLimit.limited) {
return await interaction.reply({ content: doParallelLimit.msg, ephemeral: true });
}
ratelimiter.addParallel(commandName);
}
// Check the ratelimit
const doRateLimit = await ratelimiter.check(interaction.user, commandName, command);
const doRateLimit = ratelimiter.check(interaction.user, commandName, command);
if (doRateLimit) {
return interaction.reply({ content: doRateLimit, ephemeral: true });
@ -103,18 +83,14 @@ export default {
});
if (!isOptOut) {
console.log(`[${timestamp.toISOString()}] \x1b[33m⤷\x1b[0m with args ${JSON.stringify(args)}`);
console.log(`\x1b[33m${commandName}\x1b[0m with args ${JSON.stringify(args)}`);
}
await command.execute(interaction, args, client)
.then(async () => {
const hasPrallelLimit = await ratelimiter.checkParallel(interaction.user, commandName, command);
if (hasPrallelLimit) ratelimiter.removeParallel(commandName);
});
await command.execute(interaction, args, client);
}
catch (error) {
console.error(error);
await interaction.followUp({ content: `There was an error while executing this command!\n\`${error}\``, ephemeral: true });
await interaction.followUp({ content: 'There was an error while executing this command!', ephemeral: true });
}
},
};

@ -253,7 +253,6 @@ export default {
}
// Command handling from message
// TODO: Moving that to a dedicated function that works for both messages and interactions
let hasPrefix = false;
prefixs.forEach(p => {
@ -282,40 +281,32 @@ export default {
if (!command) return;
const globalBlacklist = await db.Blacklists.findOne({ where: { type:'global', uid:message.author.id } });
// const serverBlacklist = await db.Blacklists.findOne({ where: { type:'guild', uid:message.guild.id } });
const commandBlacklist = await db.Blacklists.findOne({ where: { type:commandName, uid:message.author.id } });
if (globalBlacklist) {
return message.reply({ content: `You are globally blacklisted for the following reason: \`${globalBlacklist.reason}\``, ephemeral: true });
}
/* Server blacklist is untested
else if (serverBlacklist) {
return message.reply({ content: `This guild has been blacklisted for the following reason: \`${serverBlacklist.reason}\``, ephemeral: true });
}
*/
else if (commandBlacklist) {
return message.reply({ content: `You are blacklisted for the following reason: \`${commandBlacklist.reason}\``, ephemeral: true });
}
const userTag = message.author.username;
const userTag = message.author.tag;
const userID = message.author.id;
const isOptOut = await db.optout.findOne({ where: { userID: message.author.id } });
const timestamp = new Date();
console.log(`[${timestamp.toISOString()}] \x1b[33m${ isOptOut ? 'A user' : `${userTag} (${userID})`}\x1b[0m launched command \x1b[33m${commandName}\x1b[0m using prefix`);
if (isOptOut) {
console.log(`A user launched command \x1b[33m${commandName}\x1b[0m with prefix`);
}
else {
console.log(`\x1b[33m${userTag} (${userID})\x1b[0m launched command \x1b[33m${commandName}\x1b[0m with prefix`);
}
// Owner only check
if (command.ownerOnly && message.author.id !== ownerId) {
return message.reply({ content: '❌ This command is reserved for the owner!', ephemeral: true });
}
// Guild only check
if (command.guildOnly && !message.guild) {
return message.reply({ content: '❌ This command only work in a server!', ephemeral: true });
}
// Check if the bot has the needed permissions
if (command.clientPermissions) {
const clientMember = await message.guild.members.fetch(client.user.id);
@ -331,18 +322,8 @@ export default {
}
}
// Check if the limit of parallel execution has been reached
if (command.parallelLimit) {
const doParallelLimit = await ratelimiter.checkParallel(message.author, commandName, command);
if (doParallelLimit.limited) {
return await message.reply({ content: doParallelLimit.msg, ephemeral: true });
}
ratelimiter.addParallel(commandName);
}
// Check the ratelimit
const doRateLimit = await ratelimiter.check(message.author, commandName, command);
const doRateLimit = ratelimiter.check(message.author, commandName, command);
if (doRateLimit) {
return message.reply({ content: doRateLimit, ephemeral: true });
@ -410,16 +391,13 @@ export default {
payload = messageArgs.slice(i).join(' ');
}
if (arg.type === ApplicationCommandOptionType.Boolean && !messageArgs[i].startsWith('--')) {
continue;
}
else if (messageArgs[i].startsWith('--')) {
if (messageArgs[i].startsWith('--')) {
payloadName = payload.substring(2);
payload = true;
j--;
}
if (arg.type === ApplicationCommandOptionType.Mentionable || arg.type === ApplicationCommandOptionType.User) {
if (arg.type === ApplicationCommandOptionType.Mentionable) {
await message.guild.members.fetch();
payload = message.mentions.members.first() ? message.mentions.members.first() : message.guild.members.cache.find(u => u.user.username.toLowerCase().includes(payload.toLowerCase()));
}
@ -427,23 +405,15 @@ export default {
args[payloadName] = payload;
}
if (!isOptOut && argsLength > 0) {
console.log(`[${timestamp.toISOString()}] \x1b[33m⤷\x1b[0m with args ${JSON.stringify(args)}`);
if (!isOptOut) {
console.log(`\x1b[33m${commandName}\x1b[0m with args ${JSON.stringify(args)}`);
}
await command.execute(message, args, client)
.then(async () => {
const hasPrallelLimit = await ratelimiter.checkParallel(message.author, commandName, command);
if (hasPrallelLimit) ratelimiter.removeParallel(commandName);
});
await command.execute(message, args, client);
}
catch (error) {
console.error(error);
await message.reply({ content: `There was an error while executing this command!\n\`${error}\`` })
.catch(async () => {
await message.channel.send({ content: `There was an error while executing this command!\n\`${error}\`` });
});
await message.reply({ content: 'There was an error while executing this command!', ephemeral: true });
}
},
};

@ -128,7 +128,7 @@ export default {
.setFooter({ text: `${emote} ${reactionCount}` })
.setTimestamp();
if (reaction.message.guild.emojis.resolve(emote)) Embed.setFooter({ text: `${reactionCount}`, iconURL: reaction.message.guild.emojis.resolve(emote).url });
if (reaction.message.guild.emojis.resolve(emote)) Embed.setFooter(reactionCount, reaction.message.guild.emojis.resolve(emote).url);
let description = null;

@ -111,7 +111,7 @@ export default {
.setFooter({ text: `${emote} ${reactionCount}` })
.setTimestamp();
if (reaction.message.guild.emojis.resolve(emote)) Embed.setFooter({ text: `${reactionCount}`, iconURL: reaction.message.guild.emojis.resolve(emote).url });
if (reaction.message.guild.emojis.resolve(emote)) Embed.setFooter(reactionCount, reaction.message.guild.emojis.resolve(emote).url);
message.edit({ embeds: [Embed] });
}

@ -1,6 +1,5 @@
import { execFile } from 'node:child_process';
import { exec } from 'node:child_process';
const { statusChannel, NODE_ENV } = process.env;
import { version } from 'discord.js';
export default {
name: 'ready',
@ -10,7 +9,7 @@ export default {
global.boards = {};
const ytdlpVersion = await new Promise((resolve, reject) => {
execFile('./bin/yt-dlp', ['--version'], (err, stdout, stderr) => {
exec('./bin/yt-dlp --version', (err, stdout, stderr) => {
if (err) {
reject(stderr);
}
@ -22,7 +21,7 @@ export default {
});
const commandSize = client.commands.size;
const clientTag = client.user.username;
const clientTag = client.user.tag;
const guildSize = client.guilds.cache.size;
const channelSize = client.channels.cache.size;
const clientID = client.user.id;
@ -33,17 +32,12 @@ export default {
console.log(`${client.readyAt}`);
console.log(`There is \x1b[33m${commandSize}\x1b[0m command loaded.`);
console.log(`Running yt-dlp \x1b[33m${ytdlpVersion.replace('\n', '')}\x1b[0m`);
console.log(`Running Discord.js \x1b[33m${version}\x1b[0m`);
console.log('===========[ READY ]===========');
// If stats channel settings exist, send bot stats to it
if (statusChannel && NODE_ENV !== 'development') {
const channel = client.channels.resolve(statusChannel);
channel.send(
`Ready to serve in ${channelSize} channels on ${guildSize} servers.\n` +
`There is ${commandSize} command loaded.\n` +
`Running yt-dlp ${ytdlpVersion.replace('\n', '')}\n` +
`${client.readyAt}`);
channel.send(`Ready to serve in ${channelSize} channels on ${guildSize} servers.\nThere is ${commandSize} command loaded.\nRunning yt-dlp ${ytdlpVersion.replace('\n', '')}\n${client.readyAt}`);
}
},
};

@ -1,6 +1,4 @@
import { ActivityType } from 'discord.js';
import game from '../../json/playing.json' assert {type: 'json'};
import music from '../../json/listening.json' assert {type: 'json'};
import watch from '../../json/watching.json' assert {type: 'json'};
export default {
@ -8,44 +6,33 @@ export default {
once: true,
async execute(client) {
// Bot status
await setStatus();
setStatus();
// Change status every 30 minutes
setInterval(async () => {
await setStatus();
setStatus();
}, 1800000);
async function setStatus() {
const random = Math.floor((Math.random() * 3));
let types, status;
const random = Math.floor((Math.random() * 2));
// Random "Watching" status taken from json
if (random === 0) {
console.log('Status type: \x1b[32mWatching\x1b[0m');
status = watch[Math.floor((Math.random() * watch.length))];
let status = watch[Math.floor((Math.random() * watch.length))];
status = status + ' | Now with slash commands!';
console.log(`Setting status to: ${status}`);
types = [ ActivityType.Watching ];
client.user.setActivity(status, { type: 'WATCHING' });
}
// Random "Playing" status taken from json
else if (random === 1) {
console.log('Status type: \x1b[32mPlaying\x1b[0m');
status = game[Math.floor((Math.random() * game.length))];
let status = game[Math.floor((Math.random() * game.length))];
status = status + ' | Now with slash commands!';
console.log(`Setting status to: ${status}`);
types = [ ActivityType.Playing, ActivityType.Competing ];
client.user.setActivity(status, { type: 'PLAYING' });
}
else if (random === 2) {
console.log('Status type: \x1b[32mPlaying\x1b[0m');
status = music[Math.floor((Math.random() * music.length))];
status = status + ' | Now with slash commands!';
console.log(`Setting status to: ${status}`);
types = [ ActivityType.Listening ];
}
await client.user.setActivity(status, { type: types[Math.floor((Math.random() * types.length))] });
}
},
};

@ -1 +1 @@
["1488","14/88","14 88","niggar", "niggars", "nigger", "niggers","nigar", "kys", "kill yourself", "faggot", "faggots", "fag", "fags", "kill ur self","n\ni\ng\ng\ne\nr","n i g g e r","we must secure the existance of our people and a future for white children."]
["1488","14/88","14 88","niggar", "nigger","nigar", "kys", "kill yourself", "faggot", "fag", "kill ur self","n\ni\ng\ng\ne\nr","n i g g e r","we must secure the existance of our people and a future for white children."]

@ -1,3 +0,0 @@
[
"psychometricBussdown by oddballTheatre"
]

21
package-lock.json generated

@ -19,7 +19,7 @@
"safe-regex": "github:davisjam/safe-regex",
"sequelize": "^6.21.3",
"turndown": "^7.1.1",
"twitter-api-v2": "^1.15.0",
"twit": "^1.1.20",
"ytpplus-node": "github:Supositware/ytpplus-node"
},
"devDependencies": {
@ -5159,6 +5159,11 @@
"set-blocking": "^2.0.0"
}
},
"node_modules/oauth": {
"version": "0.9.9",
"resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.9.tgz",
"integrity": "sha512-kjdfbdtIFcfvHTdvh+pbR5RMOxrihCmMtDjOCAieetr4NoUOuRjl1q9gyGnvpPNDOalH8moHqur150XYGPQFeA=="
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@ -6817,10 +6822,16 @@
"domino": "^2.1.6"
}
},
"node_modules/twitter-api-v2": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/twitter-api-v2/-/twitter-api-v2-1.15.0.tgz",
"integrity": "sha512-Cqg3pIGhSwPyFBndpBrucdeNXecNFnYcXy3ixQ4brJHd/3k1CAtBVcX0e3s6jRYl/QIx5BmyGXS/SHEGtYZ3gw=="
"node_modules/twit": {
"version": "1.1.20",
"resolved": "https://registry.npmjs.org/twit/-/twit-1.1.20.tgz",
"integrity": "sha512-5Wb4b+1mqSxOXGrTe5O4GIB6/IQnh5anAub34M+VfAdA1g+9QfUuod4C94xf9NNcaz+rNZ/3LRUSTpn8wLMnYA==",
"dependencies": {
"oauth": "0.9.9"
},
"engines": {
"node": ">=0.6.0"
}
},
"node_modules/type": {
"version": "1.2.0",

@ -27,7 +27,7 @@
"safe-regex": "github:davisjam/safe-regex",
"sequelize": "^6.21.3",
"turndown": "^7.1.1",
"twitter-api-v2": "^1.15.0",
"twit": "^1.1.20",
"ytpplus-node": "github:Supositware/ytpplus-node"
},
"devDependencies": {

@ -16,7 +16,7 @@ You need to install the following
* HandBrakeCLI (For [download](commands/utility/download.js))
* gifsicle (For [vid2gif](commands/utility/vid2gif.js))
* gifki (For [vid2gif](commands/utility/vid2gif.js))
* Somewhere to upload files larger than the file limit, currently 25 mb. (I use a self hosted [XBackBone](https://github.com/SergiX44/XBackBone/) with the upload.sh script made from it, you can use anything else just need to be located in bin/upload.sh)
* Somewhere to upload files larger than 8 mb (I use a self hosted [XBackBone](https://github.com/SergiX44/XBackBone/) with the upload.sh script made from it, you can use anything else just need to be located in bin/upload.sh)
### Installing
```

@ -23,11 +23,11 @@ async function download(url, output) {
fs.renameSync(tmpPath, path);
fs.chmodSync(path, '755');
console.log(`${url} download finished.`);
return resolve(true);
resolve(true);
});
filePath.on('error', (err) => {
filePath.close();
return reject(err);
reject(err);
});
});
});

@ -1,17 +1,13 @@
// This is kind of useless since you can just do `./yt-dlp --update-to nightly` which I didn't know about when I wrote that.
import utils from './downloadutils.js';
(async () => {
if (process.platform !== 'linux' && process.argv[2] !== '-f') {
console.error('This script only download the linux version of yt-dlp. If you want to download anyway try again with -f or execute ./bin/yt-dlp --update-to nightly');
process.exit(1);
}
else if (process.platform !== 'linux' && process.argv[2] === '-f') {
console.log('Executed with -f. Reminder that this script only download the linux version of yt-dlp.');
}
if (process.platform !== 'linux' && process.argv[2] !== '-f') {
console.error('This script only download the linux version of yt-dlp. If you want to download anyway try again with -f');
process.exit(1);
}
else if (process.platform !== 'linux' && process.argv[2] === '-f') {
console.log('Executed with -f. Reminder that this script only download the linux version of yt-dlp.');
}
const downloadUrl = 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp';
const downloadUrl = 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp';
await utils.download(downloadUrl, './bin/yt-dlp');
});
utils.download(downloadUrl, './bin/yt-dlp');

@ -65,9 +65,7 @@ export function rand(text, interaction) {
const matches = text.matchAll(/\[.*?\]\s?/g);
for (const match of matches) {
if (search(match[0].trim(), variables)) {
text = text.replace(match[0].trim(), search(match[0].trim(), variables).value);
}
if (search(match[0].trim(), variables)) { text = text.replace(match[0].trim(), search(match[0].trim(), variables).value); }
}
return text;

@ -1,25 +1,12 @@
const ratelimit = {};
const parallelLimit = {};
const { ownerId, NODE_ENV } = process.env;
import db from '../models/index.js';
export default {
check,
addParallel,
removeParallel,
checkParallel,
};
async function check(user, commandName, commands) {
function check(user, commandName, commands) {
const userID = user.id;
const userTag = user.username;
// Don't apply the rate limit to bot owner
if (NODE_ENV !== 'development') {
if (user.id === ownerId) {
return false;
}
}
const userTag = user.tag;
if (!ratelimit[userID]) {
ratelimit[userID] = {};
@ -40,11 +27,13 @@ async function check(user, commandName, commands) {
const hours = Math.floor(minutes / 60);
const dateString = `${hours > 0 ? ` ${Math.floor(hours)} hours` : ''}${minutes > 0 ? ` ${Math.floor(minutes % 60)} minutes` : ''}${seconds > 0 ? ` ${Math.floor(seconds % 60)} seconds` : ''}`;
const isOptOut = await db.optout.findOne({ where: { userID: userID } });
const timestamp = new Date();
console.log(`[${timestamp.toISOString()}] \x1b[33m${ isOptOut ? 'A user' : `${userTag} (${userID})`}\x1b[0m is rate limited on \x1b[33m${commandName}\x1b[0m for${dateString}.`);
const isOptOut = db.optout.findOne({ where: { userID: userID } });
if (isOptOut) {
console.log(`A user is rate limited on \x1b[33m${commandName}\x1b[0m for${dateString}.`);
}
else {
console.log(`\x1b[33m${userTag} (${userID})\x1b[0m is rate limited on \x1b[33m${commandName}\x1b[0m for${dateString}.`);
}
return `You are being rate limited. You can try again in${dateString}.`;
}
}
@ -56,48 +45,4 @@ async function check(user, commandName, commands) {
}
return false;
}
async function addParallel(commandName) {
// console.log(`[ADD] Adding parallel to ${commandName}`);
if (!parallelLimit[commandName]) parallelLimit[commandName] = 0;
const prevNumber = parallelLimit[commandName];
// console.log(`[ADD] Previous parallel executions: ${prevNumber}`);
// console.log(`[ADD] Current parallel executions: ${JSON.stringify(parallelLimit)}`);
parallelLimit[commandName] = prevNumber + 1;
}
async function removeParallel(commandName) {
// console.log(`[REMOVE] Removing parallel to ${commandName}`);
// This shouldn't be possible
if (!parallelLimit[commandName]) parallelLimit[commandName] = 0;
const prevNumber = parallelLimit[commandName];
// console.log(`[REMOVE] previous number: ${prevNumber}`);
// console.log(`[REMOVE] previous parallel limit: ${JSON.stringify(parallelLimit)}`);
parallelLimit[commandName] = prevNumber - 1;
// console.log(`[REMOVE] current parallel limit: ${JSON.stringify(parallelLimit)}`);
}
async function checkParallel(user, commandName, command) {
// Don't apply the rate limit to bot owner
if (NODE_ENV !== 'development') {
if (user.id === ownerId) {
return false;
}
}
if (!parallelLimit[commandName]) parallelLimit[commandName] = 0;
// console.log(`[CHECK] command limit: ${command.parallelLimit}`);
// console.log(`[CHECK] current parallel executions: ${parallelLimit[commandName]}`);
if (parallelLimit[commandName] >= command.parallelLimit) {
return { limited: true, current: parallelLimit[commandName], max: command.parallelLimit, msg: `There are currently too many parallel execution of this command, please wait before retrying. (${parallelLimit[commandName]}/${command.parallelLimit})` };
}
return { limited: false, current: parallelLimit[commandName], max: command.parallelLimit };
}

@ -1,6 +1,6 @@
import os from 'node:os';
import { execFile } from 'node:child_process';
const { NODE_ENV, ytdlpMaxResolution } = process.env;
import { exec } from 'node:child_process';
const { NODE_ENV } = process.env;
export default {
downloadVideo,
@ -9,29 +9,25 @@ export default {
stringIsAValidurl,
compressVideo,
getVideoCodec,
getVideoSize,
getMaxFileSize,
};
async function downloadVideo(urlArg, output, format = `bestvideo[height<=?${ytdlpMaxResolution}]+bestaudio/best`) {
async function downloadVideo(urlArg, output, format = 'bestvideo*+bestaudio/best') {
await new Promise((resolve, reject) => {
execFile('./bin/yt-dlp', ['-f', format, urlArg, '-o', `${os.tmpdir()}/${output}.%(ext)s`, '--force-overwrites', '--no-playlist', '--remux-video=mp4/webm/mov', '--no-warnings'], (err, stdout, stderr) => {
exec(`./bin/yt-dlp -f ${format} "${urlArg}" -o "${os.tmpdir()}/${output}.%(ext)s" --force-overwrites --no-playlist --merge-output-format=mp4/webm/mov`, (err, stdout, stderr) => {
if (err) {
return reject(stderr);
reject(stderr);
}
if (stderr) {
console.error(stderr);
// Should already be rejected at that points
return reject(stderr);
}
console.log(NODE_ENV === 'development' ? stdout : null);
return resolve();
resolve();
});
});
}
async function upload(file) {
return await new Promise((resolve, reject) => {
execFile('./bin/upload.sh', [file], (err, stdout, stderr) => {
exec(`./bin/upload.sh ${file}`, (err, stdout, stderr) => {
if (err) {
reject(stderr);
}
@ -45,7 +41,7 @@ async function upload(file) {
async function ffmpeg(command) {
return await new Promise((resolve, reject) => {
execFile('ffmpeg', ['-hide_banner', ...command], (err, stdout, stderr) => {
exec(`ffmpeg ${command}`, (err, stdout, stderr) => {
if (err) {
reject(stderr);
}
@ -70,7 +66,7 @@ async function stringIsAValidurl(s) {
async function compressVideo(input, output, preset) {
await new Promise((resolve, reject) => {
execFile('./bin/HandBrakeCLI', ['-i', input, '-Z', preset, '-o', `${os.tmpdir()}/${output}`], (err, stdout, stderr) => {
exec(`./bin/HandBrakeCLI -i '${input}' -Z '${preset}' -o '${os.tmpdir()}/${output}'`, (err, stdout, stderr) => {
if (err) {
reject(stderr);
}
@ -84,7 +80,7 @@ async function compressVideo(input, output, preset) {
}
async function getVideoCodec(input) {
return await new Promise((resolve, reject) => {
execFile('ffprobe', ['-v', 'error', '-select_streams', 'v:0', '-show_entries', 'stream=codec_name', '-of', 'default=noprint_wrappers=1:nokey=1', input], (err, stdout, stderr) => {
exec(`ffprobe -v error -select_streams v:0 -show_entries stream=codec_name -of default=noprint_wrappers=1:nokey=1 ${input}`, (err, stdout, stderr) => {
if (err) {
reject(stderr);
}
@ -94,39 +90,4 @@ async function getVideoCodec(input) {
resolve(stdout.trim());
});
});
}
async function getVideoSize(urlArg, format = `bestvideo[height<=?${ytdlpMaxResolution}]+bestaudio/best`) {
return await new Promise((resolve, reject) => {
execFile('./bin/yt-dlp', [urlArg, '-f', format, '--no-warnings', '-O', '%(filesize,filesize_approx)s'], (err, stdout, stderr) => {
if (err) {
reject(stderr);
}
if (stderr) {
console.error(stderr);
}
resolve(stdout / 1000000.0);
});
});
}
async function getMaxFileSize(guild) {
return await new Promise((resolve) => {
const tier = guild.premiumTier;
switch (tier) {
case 0:
case 1:
resolve(25);
break;
case 2:
resolve(50);
break;
case 3:
resolve(100);
break;
default:
resolve(25);
break;
}
});
}
Loading…
Cancel
Save