Compare commits

..

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

105 changed files with 4455 additions and 6836 deletions

View file

@ -2,7 +2,7 @@ token=YourToken
clientId=BotClientId clientId=BotClientId
guildId=DevGuildId guildId=DevGuildId
ownerId=OwnerUserId ownerId=OwnerUserId
statusChannel=CHannelIdForStatus statusChannel=
uptimeURL=UptimeKumaOrWhateverStatusThingYouUseOrJustLeaveEmpty uptimeURL=UptimeKumaOrWhateverStatusThingYouUseOrJustLeaveEmpty
uptimeInterval=60 uptimeInterval=60
twiConsumer=TwitterConsumerToken twiConsumer=TwitterConsumerToken
@ -11,10 +11,3 @@ twiToken=TwitterToken
twiTokenSecret=TwitterSecretToken twiTokenSecret=TwitterSecretToken
twiChannel=ChannelWhereJustTheTwitterLinkAreSent twiChannel=ChannelWhereJustTheTwitterLinkAreSent
twiLogChannel=ChannelWhereTheDetailedInfoOfTheCommandIsSent twiLogChannel=ChannelWhereTheDetailedInfoOfTheCommandIsSent
botsggToken=APITokenForBots.gg
botsggEndpoint=https://discord.bots.gg/api/v1
stableHordeApi=0000000000
stableHordeID=0000
NODE_ENV=development
ytdlpMaxResolution=720
proxy=socks5://localhost:3128

57
.eslintrc.json Normal file
View file

@ -0,0 +1,57 @@
{
"extends": "eslint:recommended",
"env": {
"node": true,
"es6": true
},
"parser": "@babel/eslint-parser",
"parserOptions": {
"ecmaVersion": 2022,
"sourceType": "module",
"requireConfigFile": false,
"babelOptions": {
"plugins": [
"@babel/plugin-syntax-import-assertions"
]
}
},
"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"
}
}

View file

@ -33,5 +33,5 @@ labels:
**Did someone already report that bug?** **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 - [ ] No

14
.gitignore vendored
View file

@ -1,16 +1,4 @@
.env .env
node_modules/ node_modules/
bin/
config/config.json 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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

View file

@ -1,171 +0,0 @@
/* TODO
*
* To be merged with commands/AI/txt2img.js
*
*/
import { SlashCommandBuilder, EmbedBuilder, AttachmentBuilder, ButtonBuilder, ActionRowBuilder, ButtonStyle } from 'discord.js';
import fetch from 'node-fetch';
import os from 'node:os';
import fs from 'node:fs';
import stream from 'node:stream';
import util from 'node:util';
import db from '../../models/index.js';
const { stableHordeApi, stableHordeID } = process.env;
export default {
data: new SlashCommandBuilder()
.setName('img2img')
.setDescription('AI generated image with stable diffusion (If credit are low it may be slow)')
.addAttachmentOption(option =>
option.setName('image')
.setDescription('Image you want to modify')
.setRequired(true))
.addStringOption(option =>
option.setName('prompt')
.setDescription('What do you want the AI to generate?')
.setRequired(true)),
category: 'AI',
alias: ['i2i'],
async execute(interaction, args, client) {
await interaction.deferReply();
const streamPipeline = util.promisify(stream.pipeline);
const res = await fetch(args.image.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.image.name}.webp`));
const b64Image = fs.readFileSync(`${os.tmpdir()}/${args.image.name}.webp`, { encoding: 'base64' });
generate(interaction, args.prompt, client, b64Image);
},
};
async function generate(i, prompt, client, b64Img) {
console.log('Generating image');
const body = {
prompt: prompt,
params: {
n: 1,
width: 512,
height: 512,
},
cfg_scale: 9,
use_gfpgan: true,
use_real_esrgan: true,
use_ldsr: true,
use_upscaling: true,
steps: 50,
nsfw: i.channel.nsfw ? true : false,
censor_nsfw: i.channel.nsfw ? true : false,
source_image: b64Img,
source_processing: 'img2img',
shared: true,
};
const isOptOut = await db.optout.findOne({ where: { userID: i.user.id } });
if (isOptOut) {
body.shared = false;
}
const fetchParameters = {
method: 'post',
body: JSON.stringify(body),
headers: { 'Content-Type': 'application/json', 'apikey': stableHordeApi },
};
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 () => {
const checkResult = await checkGeneration(checkURL);
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);
clearInterval(checking);
return i.editReply({ content: 'No servers are currently available to fulfill your request, please try again later.' });
}
if (checkResult.wait_time === 0) {
checkURL = `https://stablehorde.net/api/v2/generate/status/${response.id}`;
}
wait_time = checkResult.wait_time;
}
else if (checkResult.done && checkResult.image) {
clearInterval(checking);
let creditResponse = await fetch(`https://stablehorde.net/api/v2/users/${stableHordeID}`);
creditResponse = await creditResponse.json();
const streamPipeline = util.promisify(stream.pipeline);
const res = await fetch(checkResult.image);
if (!res.ok) return i.editReply('An error has occured while trying to download your image.');
await streamPipeline(res.body, fs.createWriteStream(`${os.tmpdir()}/${i.id}.webp`));
const generatedImg = new AttachmentBuilder(`${os.tmpdir()}/${i.id}.webp`);
const stableEmbed = new EmbedBuilder()
.setColor(i.member ? i.member.displayHexColor : 'Navy')
.setTitle(prompt)
.setURL('https://aqualxx.github.io/stable-ui/')
.setImage(`attachment://${i.id}.webp`)
.setFooter({ text: `**Credit left: ${creditResponse.kudos}** Seed: ${checkResult.seed} worker ID: ${checkResult.worker_id} worker name: ${checkResult.worker_name}` });
const row = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(`regenerate${i.user.id}${i.id}`)
.setLabel('🔄 Regenerate')
.setStyle(ButtonStyle.Primary),
);
await i.editReply({ embeds: [stableEmbed], components: [row], files: [generatedImg] });
listenButton(client, i, prompt);
}
}, wait_time);
}
async function checkGeneration(url) {
let check = await fetch(url);
check = await check.json();
if (!check.is_possible) {
return { done: false, wait_time: -1, raw: check };
}
if (check.done) {
if (!check.generations) {
return { done: false, wait_time: check.wait_time * 1000, 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, raw: check };
}
}
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);
}
});
}

View file

@ -1,145 +0,0 @@
/* TODO
*
* To be merged with commands/AI/img2img.js
*
*/
import { SlashCommandBuilder, EmbedBuilder, AttachmentBuilder, ButtonBuilder, ActionRowBuilder, ButtonStyle } from 'discord.js';
import fetch from 'node-fetch';
import os from 'node:os';
import fs from 'node:fs';
import stream from 'node:stream';
import util from 'node:util';
import db from '../../models/index.js';
const { stableHordeApi, stableHordeID } = process.env;
export default {
data: new SlashCommandBuilder()
.setName('txt2img')
.setDescription('AI generated image with stable diffusion (If credit are low it may be slow)')
.addStringOption(option =>
option.setName('prompt')
.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);
},
};
async function generate(i, prompt, client) {
const body = {
prompt: prompt,
params: {
n: 1,
width: 512,
height: 512,
},
cfg_scale: 9,
use_gfpgan: true,
use_real_esrgan: true,
use_ldsr: true,
use_upscaling: true,
steps: 50,
nsfw: i.channel.nsfw ? true : false,
censor_nsfw: i.channel.nsfw ? true : false,
shared: true,
};
const isOptOut = await db.optout.findOne({ where: { userID: i.user.id } });
if (isOptOut) {
body.shared = false;
}
const fetchParameters = {
method: 'post',
body: JSON.stringify(body),
headers: { 'Content-Type': 'application/json', 'apikey': stableHordeApi },
};
let response = await fetch('https://stablehorde.net/api/v2/generate/async', fetchParameters);
response = await response.json();
let wait_time = 5000;
let checkURL = `https://stablehorde.net/api/v2/generate/check/${response.id}`;
const checking = setInterval(async () => {
const checkResult = await checkGeneration(checkURL);
if (checkResult === undefined) return;
if (!checkResult.done) {
if (checkResult.wait_time < 0) {
clearInterval(checking);
return i.editReply({ content: 'No servers are currently available to fulfill your request, please try again later.' });
}
if (checkResult.wait_time === 0) {
checkURL = `https://stablehorde.net/api/v2/generate/status/${response.id}`;
}
wait_time = checkResult.wait_time;
}
else if (checkResult.done && checkResult.image) {
clearInterval(checking);
let creditResponse = await fetch(`https://stablehorde.net/api/v2/users/${stableHordeID}`);
creditResponse = await creditResponse.json();
const streamPipeline = util.promisify(stream.pipeline);
const res = await fetch(checkResult.image);
if (!res.ok) return i.editReply('An error has occured while trying to download your image.');
await streamPipeline(res.body, fs.createWriteStream(`${os.tmpdir()}/${i.id}.webp`));
const generatedImg = new AttachmentBuilder(`${os.tmpdir()}/${i.id}.webp`);
const stableEmbed = new EmbedBuilder()
.setColor(i.member ? i.member.displayHexColor : 'Navy')
.setTitle(prompt)
.setURL('https://aqualxx.github.io/stable-ui/')
.setImage(`attachment://${i.id}.webp`)
.setFooter({ text: `**Credit left: ${creditResponse.kudos}** Seed: ${checkResult.seed} worker ID: ${checkResult.worker_id} worker name: ${checkResult.worker_name}` });
const row = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(`regenerate${i.user.id}${i.id}`)
.setLabel('🔄 Regenerate')
.setStyle(ButtonStyle.Primary),
);
await i.editReply({ embeds: [stableEmbed], components: [row], files: [generatedImg] });
listenButton(client, i, prompt);
}
}, wait_time);
}
async function checkGeneration(url) {
let check = await fetch(url);
check = await check.json();
if (!check.is_possible) {
return { done: false, wait_time: -1 };
}
if (check.done) {
if (!check.generations) {
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 };
}
}
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);
}
});
}

View file

@ -1,62 +0,0 @@
import { SlashCommandBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder, PermissionFlagsBits } from 'discord.js';
import db from '../../models/index.js';
export default {
data: new SlashCommandBuilder()
.setName('autoresponse')
.setDescription('Enable or disable autoresponse')
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages),
category: 'admin',
async execute(interaction, args, client) {
const autoresponseStat = await db.autoresponseStat.findOne({ where: { serverID: interaction.guild.id } });
if (!autoresponseStat) {
const body = { serverID: interaction.guild.id, stat: 'enable' };
await db.autoresponseStat.create(body);
return await interaction.reply({ content: 'Autoresponse has been enabled.', ephemeral: true });
}
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),
);
if (autoresponseStat.stat === 'enable') {
await interaction.reply({ content: 'Autoresponse is already enabled, do you wish to disable it?', components: [row], ephemeral: true });
}
else {
const body = { serverID: interaction.guild.id, stat: 'enable' };
await db.autoresponseStat.update(body, { where: { serverID: interaction.guild.id } });
return interaction.editReply({ content: 'Auto response has been enabled.', ephemeral: true });
}
return listenButton(client, interaction, interaction.user);
},
};
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 });
}
});
}

View file

@ -1,74 +0,0 @@
import { SlashCommandBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder, PermissionFlagsBits } from 'discord.js';
import db from '../../models/index.js';
export default {
data: new SlashCommandBuilder()
.setName('bye')
.setDescription('Set a leave message')
.addStringOption(option =>
option.setName('message')
.setDescription('The message you want the bot to say when someone leave in the current channel.')),
category: 'admin',
userPermissions: [PermissionFlagsBits.ManageChannels],
async execute(interaction, args, client) {
const leave = await db.leaveChannel.findOne({ where: { guildID: interaction.guild.id } });
if (!leave && !args.message) {
return interaction.reply({ content: 'You need a message for me to say anything!', ephemeral: true });
}
else if (!leave) {
const body = { guildID: interaction.guild.id, channelID: interaction.channel.id, message: args.message };
await db.leaveChannel.create(body);
return interaction.reply({ content: `The leave message have been set with ${args.message}`, ephemeral: true });
}
const row = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(`edit${interaction.user.id}${interaction.id}`)
.setLabel('Edit')
.setStyle(ButtonStyle.Primary),
)
.addComponents(
new ButtonBuilder()
.setCustomId(`remove${interaction.user.id}${interaction.id}`)
.setLabel('Remove')
.setStyle(ButtonStyle.Danger),
)
.addComponents(
new ButtonBuilder()
.setCustomId(`nothing${interaction.user.id}${interaction.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);
},
};
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 });
}
});
}

View file

@ -1,62 +0,0 @@
import { SlashCommandBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder, PermissionFlagsBits } from 'discord.js';
import db from '../../models/index.js';
export default {
data: new SlashCommandBuilder()
.setName('quotation')
.setDescription('Enable or disable quotations')
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages),
category: 'admin',
async execute(interaction, args, client) {
const quotationstat = await db.quotationStat.findOne({ where: { serverID: interaction.guild.id } });
if (!quotationstat) {
const body = { serverID: interaction.guild.id, stat: 'enable' };
await db.quotationStat.create(body);
return await interaction.reply({ content: 'Quotation has been enabled.', ephemeral: true });
}
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),
);
if (quotationstat.stat === 'enable') {
await interaction.reply({ content: 'Quotation is already enabled, do you wish to disable it?', components: [row], ephemeral: true });
}
else {
const body = { serverID: interaction.guild.id, stat: 'enable' };
await db.autoresponseStat.update(body, { where: { serverID: interaction.guild.id } });
return interaction.editReply({ content: 'Quotation has been enabled.', ephemeral: true });
}
return listenButton(client, interaction, interaction.user);
},
};
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 });
}
});
}

View file

@ -1,38 +0,0 @@
import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js';
import fs from 'node:fs';
export default {
data: new SlashCommandBuilder()
.setName('shameboard')
.setDescription('Set shameboard to the current channel.')
.addStringOption(option =>
option.setName('emote')
.setDescription('The emote that should be used to enter the shameboard.'))
.addStringOption(option =>
option.setName('count')
.setDescription('How many react for it to enter shameboard.'))
.addBooleanOption(option =>
option.setName('remove')
.setDescription('Remove the shameboard')
.setRequired(false)),
category: 'admin',
userPermissions: [PermissionFlagsBits.ManageChannels],
async execute(interaction, args) {
if (args.remove) {
fs.unlink(`./json/board/shame${interaction.guild.id}.json`, (err) => {
if (err) {return interaction.reply('There is no shameboard');}
return interaction.reply('Deleted the shameboard');
});
}
else {
if (!args.emote || !args.count) return interaction.reply('You are missing the emote or the count arg!');
fs.writeFile(`./json/board/shame${interaction.guild.id}.json`, `{"shameboard": "${interaction.channel.id}", "emote": "${args.emote}", "count": "${args.count}"}`, (err) => {
if (err) {
console.log(err);
}
});
return interaction.reply(`This channel have been set as the shameboard with ${args.emote} with the minimum of ${args.count}`);
}
},
};

View file

@ -1,38 +0,0 @@
import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js';
import fs from 'node:fs';
export default {
data: new SlashCommandBuilder()
.setName('starboard')
.setDescription('Set starboard to the current channel.')
.addStringOption(option =>
option.setName('emote')
.setDescription('The emote that should be used to enter the starboard.'))
.addStringOption(option =>
option.setName('count')
.setDescription('How many react for it to enter starboard.'))
.addBooleanOption(option =>
option.setName('remove')
.setDescription('Remove the starboard')
.setRequired(false)),
category: 'admin',
userPermissions: [PermissionFlagsBits.ManageChannels],
async execute(interaction, args) {
if (args.remove) {
fs.unlink(`./json/board/star${interaction.guild.id}.json`, (err) => {
if (err) {return interaction.reply('There is no starboard');}
return interaction.reply('Deleted the starboard');
});
}
else {
if (!args.emote || !args.count) return interaction.reply('You are missing the emote or the count arg!');
fs.writeFile(`./json/board/star${interaction.guild.id}.json`, `{"starboard": "${interaction.channel.id}", "emote": "${args.emote}", "count": "${args.count}"}`, (err) => {
if (err) {
console.log(err);
}
});
return interaction.reply(`This channel have been set as the starboard with ${args.emote} with the minimum of ${args.count}`);
}
},
};

View file

@ -1,127 +0,0 @@
import { SlashCommandBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder, PermissionFlagsBits, PermissionsBitField } from 'discord.js';
import os from 'node:os';
import fs from 'node:fs';
import db from '../../models/index.js';
const { ownerId } = process.env;
export default {
data: new SlashCommandBuilder()
.setName('tag')
.setDescription('Create custom autoresponse')
.addStringOption(option =>
option.setName('trigger')
.setDescription('The strings that will trigger the tag')
.setRequired(false))
.addStringOption(option =>
option.setName('response')
.setDescription('What it will answer back')
.setRequired(false))
.addBooleanOption(option =>
option.setName('remove')
.setDescription('(ADMIN ONLY!) Remove the tag')
.setRequired(false))
.addBooleanOption(option =>
option.setName('list')
.setDescription('List all the tags for the server')
.setRequired(false)),
category: 'admin',
userPermissions: [PermissionFlagsBits.ManageChannels],
async execute(interaction, args, client) {
await interaction.deferReply();
if (args.list) {
let tagList = await db.Tag.findAll({ attributes: ['trigger', 'response', 'ownerID'], where: { serverID: interaction.guild.id } });
if (args.trigger) {
tagList = await db.Tag.findOne({ attributes: ['trigger', 'response', 'ownerID'], where: { trigger: args.trigger, serverID: interaction.guild.id } });
}
if (!tagList) return interaction.editReply('It looks like the server has no tags.');
const path = `${os.tmpdir()}/${interaction.guild.id}.json`;
fs.writeFile(path, JSON.stringify(tagList, null, 2), function(err) {
if (err) return console.error(err);
});
return interaction.editReply({ files: [path] });
}
const tag = await db.Tag.findOne({ where: { trigger: args.trigger, serverID: interaction.guild.id } });
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) {
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`);
}
}
else {
return interaction.editReply('Did not find the specified tag, are you sure it exist?');
}
}
if (!args.trigger) return interaction.editReply('You need to specify what you want me to respond to.');
if (!args.response) return interaction.editReply('You need to specify what you want me to answer with.');
if (!tag) {
const body = { trigger: args.trigger, response: args.response, ownerID: interaction.user.id, serverID: interaction.guild.id };
await db.Tag.create(body);
return interaction.editReply(`tag have been set to ${args.trigger} : ${args.response}`);
}
else if (tag.get('ownerID') == interaction.user.id || interaction.member.permissionsIn(interaction.channel).has('ADMINISTRATOR') || interaction.user.id == ownerId) {
const row = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(`edit${interaction.user.id}${interaction.id}`)
.setLabel('Edit')
.setStyle(ButtonStyle.Primary),
)
.addComponents(
new ButtonBuilder()
.setCustomId(`remove${interaction.user.id}${interaction.id}`)
.setLabel('Remove')
.setStyle(ButtonStyle.Danger),
)
.addComponents(
new ButtonBuilder()
.setCustomId(`nothing${interaction.user.id}${interaction.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);
}
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: [] });
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 });
}
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 {
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
}
});
}

View file

@ -1,75 +0,0 @@
import { SlashCommandBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder, PermissionFlagsBits } from 'discord.js';
import db from '../../models/index.js';
export default {
data: new SlashCommandBuilder()
.setName('welcome')
.setDescription('Set a join message')
.addStringOption(option =>
option.setName('message')
.setDescription('The message you want the bot to say when someone join in the current channel.')),
category: 'admin',
userPermissions: [PermissionFlagsBits.ManageChannels],
async execute(interaction, args, client) {
const join = await db.joinChannel.findOne({ where: { guildID: interaction.guild.id } });
if (!join && !args.message) {
return interaction.reply({ content: 'You need a message for me to say anything!', 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.reply({ content: `The join message have been set with ${args.message}`, ephemeral: true });
}
const row = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(`edit${interaction.user.id}${interaction.id}`)
.setLabel('Edit')
.setStyle(ButtonStyle.Primary),
)
.addComponents(
new ButtonBuilder()
.setCustomId(`remove${interaction.user.id}${interaction.id}`)
.setLabel('Remove')
.setStyle(ButtonStyle.Danger),
)
.addComponents(
new ButtonBuilder()
.setCustomId(`nothing${interaction.user.id}${interaction.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);
},
};
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 });
}
});
}

View file

@ -1,10 +1,10 @@
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from '@discordjs/builders';
import { EmbedBuilder } from 'discord.js'; import { MessageEmbed } from 'discord.js';
import TurndownService from 'turndown'; import TurndownService from 'turndown';
const turndown = new TurndownService(); const turndown = new TurndownService();
import fetch from 'node-fetch'; import fetch from 'node-fetch';
import fourChan from '../../json/4chan.json' with {type: 'json'}; import fourChan from '../../json/4chan.json' assert {type: 'json'};
export default { export default {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
@ -14,9 +14,8 @@ export default {
option.setName('board') option.setName('board')
.setDescription('The board you wish to see') .setDescription('The board you wish to see')
.setRequired(true)), .setRequired(true)),
category: 'fun', async execute(interaction) {
async execute(interaction, args) { let board = interaction.options.getString('board');
let board = args.board;
if (fourChan[board] == undefined) { if (fourChan[board] == undefined) {
return interaction.reply({ content: 'Uh oh! The board you are looking for does not exist? You think this is a mistake? Please send a feedback telling me so!', ephemeral: true }); return interaction.reply({ content: 'Uh oh! The board you are looking for does not exist? You think this is a mistake? Please send a feedback telling me so!', ephemeral: true });
@ -66,8 +65,8 @@ export default {
title = 'No title'; title = 'No title';
} }
const FourchanEmbed = new EmbedBuilder() const FourchanEmbed = new MessageEmbed()
.setColor(interaction.member ? interaction.member.displayHexColor : 'Navy') .setColor(interaction.member ? interaction.member.displayHexColor : 'NAVY')
.setTitle(turndown.turndown(title)) .setTitle(turndown.turndown(title))
.setDescription(turndown.turndown(description)) .setDescription(turndown.turndown(description))
.setImage(`https://i.4cdn.org/${board}/${response.threads[i].posts[0].tim}${response.threads[i].posts[0].ext}`) .setImage(`https://i.4cdn.org/${board}/${response.threads[i].posts[0].tim}${response.threads[i].posts[0].ext}`)

View file

@ -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;
}
});
}
}

View file

@ -1,5 +1,5 @@
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from '@discordjs/builders';
import { PermissionFlagsBits } from 'discord.js'; import { Permissions } from 'discord.js';
export default { export default {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('fakeuser') .setName('fakeuser')
@ -16,18 +16,14 @@ export default {
option.setName('image') option.setName('image')
.setDescription('Optional attachment.') .setDescription('Optional attachment.')
.setRequired(false)), .setRequired(false)),
category: 'fun', clientPermissions: [ Permissions.FLAGS.MANAGE_WEBHOOKS ],
clientPermissions: [ PermissionFlagsBits.ManageWebhooks ], async execute(interaction) {
async execute(interaction, args) {
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({ ephemeral: true });
await interaction.guild.members.fetch(); const attachment = interaction.options.getAttachment('image');
const member = args.user; const message = interaction.options.getString('message');
const message = args.message; const member = interaction.options.getMentionable('user');
const attachment = args.image;
const username = member.nickname ? member.nickname : member.user.username;
const webhook = await interaction.channel.createWebhook({ const webhook = await interaction.channel.createWebhook(member.user.username, {
name: username,
avatar: member.user.displayAvatarURL(), avatar: member.user.displayAvatarURL(),
reason: `Fakebot/user command triggered by: ${interaction.user.username}`, reason: `Fakebot/user command triggered by: ${interaction.user.username}`,
}); });
@ -38,12 +34,6 @@ export default {
await webhook.send({ content: message }); await webhook.send({ content: message });
} }
await webhook.delete(`Fakebot/user command triggered by: ${interaction.user.username}`); await webhook.delete(`Fakebot/user command triggered by: ${interaction.user.username}`);
if (interaction.isMessage) {
await interaction.delete();
await interaction.deleteReply();
}
else {
await interaction.editReply({ content: `Faked the user ${member}` }); await interaction.editReply({ content: `Faked the user ${member}` });
}
}, },
}; };

View file

@ -1,93 +0,0 @@
// Tried something to make it work purely with slash commands but it did not work.
// The interaction created from a modal lack the showModal function so I can't just call another modal on top
// A "solution" for this that I could see is creating a button between each response asking the player to continue or stop which would create a new interaction and (maybe) allow to display a new modal
import { SlashCommandBuilder, ActionRowBuilder, TextInputBuilder, SelectMenuBuilder, ModalBuilder, TextInputStyle, InteractionType } from 'discord.js';
export default {
data: new SlashCommandBuilder()
.setName('guess')
.setDescription('Guess the number'),
category: 'fun',
async execute(interaction, args, client) {
const row = new ActionRowBuilder()
.addComponents(
new SelectMenuBuilder()
.setCustomId('difficulty')
.setPlaceholder('Nothing selected')
.addOptions([
{ label: 'Easy', value: '100' },
{ label: 'Normal', value: '1000' },
{ label: 'Hard', value: '10000' },
]),
);
await interaction.reply({ content: 'Which difficulty do you want to play?', ephemeral: true, components: [row] });
let numberTry = 0;
let secretnumber = 0;
client.on('interactionCreate', async (interactionMenu) => {
if (interaction.user !== interactionMenu.user) return;
const modal = new ModalBuilder()
.setCustomId('guessModal')
.setTitle('Your guess');
const textRow = new ActionRowBuilder()
.addComponents(
new TextInputBuilder()
.setCustomId('input')
.setLabel('What is the number?')
.setStyle(TextInputStyle.Short),
);
modal.addComponents(textRow);
async function tryAgain(input) {
if (input != secretnumber) {
if (input > secretnumber) {
modal.setTitle('Its less!\nWhat is the number?');
}
else if (input < secretnumber) {
modal.setTitle('Its more!\nWhat is the number?');
}
}
await interactionMenu.showModal(modal);
}
async function checkNumber(input) {
numberTry++;
if (input.toLowerCase() === 'stop') {
return interaction.reply('Ok, let\'s stop playing :(');
}
else if (input != secretnumber) {
console.log('trying again');
tryAgain(input);
}
else if (numberTry > 1) {
return interaction.reply(`Congratulations! You won! It took you ${numberTry} turns!`);
}
else {
return interaction.reply('Congratulations! You won! It took you 1 Turn!');
}
}
if (interactionMenu.type === InteractionType.ModalSubmit) {
if (interactionMenu.customId === 'guessModal') {
const input = interactionMenu.fields.getTextInputValue('input');
checkNumber(input);
}
}
else if (interactionMenu.isSelectMenu()) {
if (interactionMenu.customId === 'difficulty') {
secretnumber = Math.floor((Math.random() * parseInt(interactionMenu.values[0])) + 1);
console.log(secretnumber);
// await interaction.followUp({ content: 'What is the number?', ephemeral: true });
await interactionMenu.showModal(modal);
}
}
});
},
};

View file

@ -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;
}
});
}
}

View file

@ -1,12 +1,10 @@
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from '@discordjs/builders';
import fetch from 'node-fetch'; import fetch from 'node-fetch';
export default { export default {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('inspirobot') .setName('inspirobot')
.setDescription('Get an image from inspirobot'), .setDescription('Get an image from inspirobot'),
category: 'fun',
alias: ['ib'],
async execute(interaction) { async execute(interaction) {
fetch('http://inspirobot.me/api?generate=true') fetch('http://inspirobot.me/api?generate=true')
.then(res => res.text()) .then(res => res.text())

View file

@ -1,4 +1,5 @@
import { SlashCommandBuilder, EmbedBuilder } from 'discord.js'; import { SlashCommandBuilder } from '@discordjs/builders';
import { MessageEmbed } from 'discord.js';
import fetch from 'node-fetch'; import fetch from 'node-fetch';
export default { export default {
@ -9,11 +10,10 @@ export default {
option.setName('subreddit') option.setName('subreddit')
.setDescription('The subreddit you wish to see') .setDescription('The subreddit you wish to see')
.setRequired(true)), .setRequired(true)),
category: 'fun', async execute(interaction) {
async execute(interaction, args) {
await interaction.deferReply({ ephemeral: false }); await interaction.deferReply({ ephemeral: false });
const subreddit = args.subreddit;
fetch('https://www.reddit.com/r/' + subreddit + '.json?limit=100').then((response) => { fetch('https://www.reddit.com/r/' + interaction.options.getString('subreddit') + '.json?limit=100').then((response) => {
return response.json(); return response.json();
}).then((response) => { }).then((response) => {
if (response.error == 404) { if (response.error == 404) {
@ -27,15 +27,10 @@ export default {
if (response.data.children[i].data.over_18 == true && !interaction.channel.nsfw) { if (response.data.children[i].data.over_18 == true && !interaction.channel.nsfw) {
return interaction.editReply('No nsfw'); return interaction.editReply('No nsfw');
} }
const redditEmbed = new MessageEmbed()
let description = response.data.children[i].data.selftext; .setColor(interaction.member ? interaction.member.displayHexColor : 'NAVY')
if (description === '') {
description = 'No description.';
}
const redditEmbed = new EmbedBuilder()
.setColor(interaction.member ? interaction.member.displayHexColor : 'Navy')
.setTitle(response.data.children[i].data.title) .setTitle(response.data.children[i].data.title)
.setDescription(description) .setDescription(response.data.children[i].data.selftext)
.setURL('https://reddit.com' + response.data.children[i].data.permalink) .setURL('https://reddit.com' + response.data.children[i].data.permalink)
.setFooter({ text: `/r/${response.data.children[i].data.subreddit} | ⬆ ${response.data.children[i].data.ups} 🗨 ${response.data.children[i].data.num_comments}` }); .setFooter({ text: `/r/${response.data.children[i].data.subreddit} | ⬆ ${response.data.children[i].data.ups} 🗨 ${response.data.children[i].data.num_comments}` });

18
commands/fun/s.js Normal file
View file

@ -0,0 +1,18 @@
import { SlashCommandBuilder } from '@discordjs/builders';
export default {
data: new SlashCommandBuilder()
.setName('s')
.setDescription('What could this be 🤫')
.addStringOption(option =>
option.setName('something')
.setDescription('🤫')
.setRequired(true)),
async execute(interaction) {
const command = interaction.options.getString('something');
if (command === 'levertowned') {
interaction.reply('Hello buddy bro <:youngtroll:488559163832795136> <@434762632004894746>');
}
},
};

View file

@ -1,14 +1,14 @@
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from '@discordjs/builders';
import { EmbedBuilder } from 'discord.js'; import { MessageEmbed } from 'discord.js';
import { TwitterApi } from 'twitter-api-v2'; import Twit from 'twit';
import fetch from 'node-fetch'; import fetch from 'node-fetch';
import os from 'node:os'; import os from 'node:os';
import fs from 'node:fs'; import fs from 'node:fs';
import util from 'node:util';
import stream from 'node:stream';
import db from '../../models/index.js'; import db from '../../models/index.js';
import wordToCensor from '../../json/censor.json' with {type: 'json'}; import wordToCensor from '../../json/censor.json' assert {type: 'json'};
import dotenv from 'dotenv';
dotenv.config();
const { twiConsumer, twiConsumerSecret, twiToken, twiTokenSecret, twiChannel, twiLogChannel } = process.env; const { twiConsumer, twiConsumerSecret, twiToken, twiTokenSecret, twiChannel, twiLogChannel } = process.env;
const Blacklists = db.Blacklists; const Blacklists = db.Blacklists;
@ -16,55 +16,33 @@ const Blacklists = db.Blacklists;
export default { export default {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('tweet') .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 => .addStringOption(option =>
option.setName('content') 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)) .setRequired(false))
.addAttachmentOption(option => .addAttachmentOption(option =>
option.setName('image') option.setName('image')
.setDescription('Optional attachment (Image only.)') .setDescription('Optional attachment (Image only.)')
.setRequired(false)), .setRequired(false)),
category: 'fun',
ratelimit: 3, ratelimit: 3,
cooldown: 86400, cooldown: 3600,
guildOnly: true, async execute(interaction) {
async execute(interaction, args, client) { if (!interaction.options.getString('content') && !interaction.options.getAttachment('image')) {
const content = args.content;
const attachment = args.image;
if (!content && !attachment) {
return interaction.reply({ content: 'Uh oh! You are missing any content for me to tweet!', ephemeral: true }); return interaction.reply({ content: 'Uh oh! You are missing any content for me to tweet!', ephemeral: true });
} }
await interaction.deferReply({ ephemeral: false }); await interaction.deferReply({ ephemeral: false });
let tweet = content; let tweet = interaction.options.getString('content');
const attachment = interaction.options.getAttachment('image');
const date = new Date(); 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 account is less than 6 months old don't accept the tweet ( alt prevention )
if (interaction.user.createdAt > date.setMonth(date.getMonth() - 6)) { 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!' }); await interaction.editReply({ content: 'Your account is too new to be able to use this command!' });
return; 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()); date.setTime(Date.now());
// If account is less than 1 year old don't accept attachment // If account is less than 1 year old don't accept attachment
@ -73,33 +51,20 @@ export default {
return; return;
} }
if (tweet) {
// remove zero width space // remove zero width space
if (tweet) {
tweet = tweet.replace('', ''); 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) {
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;
}
});
// Detect banned word (Blacklist the user directly) // 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.substr(0, tweet.length - 1)) || wordToCensor.includes(tweet.substr(1, tweet.length))) {
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.' }; const body = { type:'tweet', uid: interaction.user.id, reason: 'Automatic ban from banned word.' };
Blacklists.create(body); Blacklists.create(body);
await interaction.editReply({ content: 'Sike, you just posted cringe! Enjoy the blacklist :)' }); await interaction.editReply({ content: 'Sike, you just posted cringe! Enjoy the blacklist :)' });
return; return;
} }
*/
// Very simple link detection // 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')) { 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,23 +78,22 @@ export default {
} }
} }
const T = new Twit({
const userClient = new TwitterApi({ consumer_key: twiConsumer,
appKey: twiConsumer, consumer_secret: twiConsumerSecret,
appSecret: twiConsumerSecret, access_token: twiToken,
accessToken: twiToken, access_token_secret: twiTokenSecret,
accessSecret: twiTokenSecret,
}); });
try { try {
// Make sure there is an attachment and if its an image // Make sure there is an attachment and if its an image
if (attachment) { if (attachment) {
if (attachment.name.toLowerCase().endsWith('.jpg') || attachment.name.toLowerCase().endsWith('.png') || attachment.name.toLowerCase().endsWith('.gif')) { if (attachment.name.toLowerCase().endsWith('.jpg') || attachment.name.toLowerCase().endsWith('.png') || attachment.name.toLowerCase().endsWith('.gif')) {
const streamPipeline = util.promisify(stream.pipeline); fetch(attachment.url)
const res = await fetch(attachment.url); .then(res => {
if (!res.ok) return interaction.editReply('An error has occured while trying to download your image.'); const dest = fs.createWriteStream(`${os.tmpdir()}/${attachment.name}`);
await streamPipeline(res.body, fs.createWriteStream(`${os.tmpdir()}/${attachment.name}`)); res.body.pipe(dest);
dest.on('finish', () => {
const file = fs.statSync(`${os.tmpdir()}/${attachment.name}`); const file = fs.statSync(`${os.tmpdir()}/${attachment.name}`);
const fileSize = file.size / 1000000.0; const fileSize = file.size / 1000000.0;
@ -140,8 +104,19 @@ export default {
return interaction.editReply({ content: 'Gifs can\'t be larger than 15 MB!' }); return interaction.editReply({ content: 'Gifs can\'t be larger than 15 MB!' });
} }
const image = await userClient.v1.uploadMedia(`${os.tmpdir()}/${attachment.name}`); const b64Image = fs.readFileSync(`${os.tmpdir()}/${attachment.name}`, { encoding: 'base64' });
Tweet(image); 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 { else {
await interaction.editReply({ content: 'File type not supported, you can only send jpg/png/gif' }); await interaction.editReply({ content: 'File type not supported, you can only send jpg/png/gif' });
@ -158,47 +133,70 @@ export default {
return; return;
} }
async function Tweet(img) { function Tweet(data) {
let options = null; let options = {
if (img) { status: tweet,
options = { media: { media_ids: new Array(img) } }; };
if (data && tweet) {
options = {
status: tweet,
media_ids: new Array(data.media_id_string),
};
}
else if (data) {
options = {
media_ids: new Array(data.media_id_string),
};
} }
const tweeted = await userClient.v2.tweet(tweet, options);
const tweetid = tweeted.data.id; T.post('statuses/update', options, function(err, response) {
const FunnyWords = ['oppaGangnamStyle', '69', '420', 'cum', 'funnyMan', 'GUCCISmartToilet', 'TwitterForClowns', 'fart', 'ok', 'hi', 'howAreYou', 'WhatsNinePlusTen', '21']; if (err) {
const TweetLink = `https://vxtwitter.com/${FunnyWords[Math.floor((Math.random() * FunnyWords.length))]}/status/${tweetid}`; // 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!' });
}
let channel = await client.channels.resolve(twiChannel); const tweetid = response.id_str;
const FunnyWords = ['oppaGangnamStyle', '69', '420', 'cum', 'funnyMan', 'GUCCISmartToilet', 'TwitterForClowns', 'fart', 'mcDotnamejeffDotxyz', '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 = interaction.client.channels.resolve(twiChannel);
channel.send(TweetLink); channel.send(TweetLink);
const Embed = new EmbedBuilder() const Embed = new MessageEmbed()
.setAuthor({ name: interaction.user.username, iconURL: interaction.user.displayAvatarURL() }) .setAuthor({ name: interaction.user.username, iconURL: interaction.user.displayAvatarURL() })
.setDescription(tweet ? tweet : 'No content.') .setDescription(tweet)
.addFields( .addField('Link', TweetLink, true)
{ name: 'Link', value: TweetLink, inline: true }, .addField('Tweet ID', tweetid, true)
{ name: 'Tweet ID', value: tweetid, inline: true }, .addField('Channel ID', interaction.channel.id, true)
{ name: 'Channel ID', value: interaction.channel.id, inline: true }, .addField('Messsage ID', interaction.id, true)
{ name: 'Message ID', value: interaction.id, inline: true }, .addField('Author', `${interaction.user.username} (${interaction.user.id})`, true)
{ name: 'Author', value: `${interaction.user.username} (${interaction.user.id})`, inline: true },
)
.setTimestamp(); .setTimestamp();
if (interaction.guild) { if (interaction.guild) {
Embed.addFields( Embed.addField('Guild', `${interaction.guild.name} (${interaction.guild.id})`, true);
{ name: 'Guild', value: `${interaction.guild.name} (${interaction.guild.id})`, inline: true }, Embed.addField('message link', `https://discord.com/channels/${interaction.guild.id}/${interaction.channel.id}/${interaction.id}`);
{ name: 'message link', value: `https://discord.com/channels/${interaction.guild.id}/${interaction.channel.id}/${interaction.id}`, inline: true },
);
} }
else { else {
Embed.addFields({ name: 'message link', value: `https://discord.com/channels/@me/${interaction.channel.id}/${interaction.id}` }); Embed.addField('message link', `https://discord.com/channels/@me/${interaction.channel.id}/${interaction.id}`);
} }
if (attachment) Embed.setImage(attachment.url); if (attachment) Embed.setImage(attachment.url);
channel = await client.channels.resolve(twiLogChannel); channel = interaction.client.channels.resolve(twiLogChannel);
channel.send({ embeds: [Embed] }); channel.send({ embeds: [Embed] });
return interaction.editReply({ content: `Go see ur epic tweet ${TweetLink}` }); return interaction.editReply({ content: `Go see ur epic tweet ${TweetLink}` });
});
} }
}, },
}; };

View file

@ -1,90 +0,0 @@
import { SlashCommandBuilder } from 'discord.js';
import fs from 'node:fs';
import os from 'node:os';
import YTPGenerator from 'ytpplus-node';
export default {
data: new SlashCommandBuilder()
.setName('ytp')
.setDescription('Generate a YTP')
.addBooleanOption(option =>
option.setName('force')
.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`);
// Read userVid folder and select random vid and only take .mp4
const mp4 = [];
const asset = [];
// Count number of total vid
fs.readdirSync('./asset/ytp/userVid/').forEach(file => {
if (file.endsWith('mp4')) {
mp4.push(file);
}
});
const MAX_CLIPS = 20;
// Select random vid depending on the amount of MAX_CLIPS
for (let i = 0; i < MAX_CLIPS; i++) {
const random = Math.floor(Math.random() * mp4.length);
const vid = `./asset/ytp/userVid/${mp4[random]}`;
if (mp4[random].endsWith('mp4')) {
if (!asset.includes(vid)) {
asset.push(vid);
}
}
}
const loadingmsg = await interaction.reply(`Processing, this can take a ***long*** time, i'll ping you when I finished <a:loadingmin:527579785212329984>\nSome info: There are currently ${mp4.length} videos, why not add yours? You can do so with the \`\`addytp\`\` command.\nLike ytp? Why not check out https://ytp.namejeff.xyz/`);
const options = {
debug: false,
MAX_STREAM_DURATION: Math.floor((Math.random() * 3) + 1),
sources: './asset/ytp/sources/',
sounds: './asset/ytp/sounds/',
music: './asset/ytp/music/',
resources: './asset/ytp/resources/',
temp: os.tmpdir(),
sourceList: asset,
intro: args.force ? './asset/ytp/intro.mp4' : null,
outro: './asset/ytp/outro.mp4',
OUTPUT_FILE: `${os.tmpdir()}/${interaction.id}_YTP.mp4`,
MAX_CLIPS: MAX_CLIPS,
transitions: true,
showFileNames: true,
effects: {
effect_RandomSound: true,
effect_RandomSoundMute: true,
effect_Reverse: true,
effect_Chorus: true,
effect_Vibrato: true,
effect_HighPitch: true,
effect_LowPitch: true,
effect_SpeedUp: true,
effect_SlowDown: true,
effect_Dance: true,
effect_Squidward: true,
effect_How: true,
},
};
await 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`] })
.catch(err => {
console.error(err);
return interaction.followUp({ files: [`./asset/ytp/error${Math.floor(Math.random() * 2) + 1}.mp4`] });
});
})
.catch(err => {
console.error(err);
loadingmsg.delete();
return interaction.followUp({ files: [`./asset/ytp/error${Math.floor(Math.random() * 2) + 1}.mp4`] });
});
},
};

View file

@ -1,21 +1,20 @@
// TODO import { SlashCommandBuilder } from '@discordjs/builders';
// Switch to 'twitter-api-v2'
import { SlashCommandBuilder } from 'discord.js';
import Twit from 'twit'; import Twit from 'twit';
import dotenv from 'dotenv';
dotenv.config();
const { twiConsumer, twiConsumerSecret, twiToken, twiTokenSecret } = process.env; const { twiConsumer, twiConsumerSecret, twiToken, twiTokenSecret } = process.env;
export default { export default {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('deletetweet') .setName('deletewteet')
.setDescription('Delete a tweet') .setDescription('Delete a tweet')
.addStringOption(option => .addStringOption(option =>
option.setName('tweetid') option.setName('tweetid')
.setDescription('The id of the tweet you wish to delete.') .setDescription('The id of the tweet you wish to delete.')
.setRequired(true)), .setRequired(true)),
category: 'owner',
ownerOnly: true, ownerOnly: true,
async execute(interaction, args) { async execute(interaction) {
await interaction.deferReply(); await interaction.deferReply();
try { try {
const T = new Twit({ const T = new Twit({
@ -26,7 +25,7 @@ export default {
}); });
T.post('statuses/destroy', { T.post('statuses/destroy', {
id: args.tweetid, id: interaction.options.getString('tweetid'),
}); });
return interaction.editReply('Tweet have been deleted!'); return interaction.editReply('Tweet have been deleted!');
} }

View file

@ -1,10 +1,9 @@
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from '@discordjs/builders';
export default { export default {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('die') .setName('die')
.setDescription('Kill the bot'), .setDescription('Kill the bot'),
category: 'owner',
ownerOnly: true, ownerOnly: true,
async execute(interaction) { async execute(interaction) {
console.log('\x1b[31m\x1b[47m\x1b[5mSHUTING DOWN!!!!!\x1b[0m'); console.log('\x1b[31m\x1b[47m\x1b[5mSHUTING DOWN!!!!!\x1b[0m');

View file

@ -1,69 +0,0 @@
import { SlashCommandBuilder, EmbedBuilder } from 'discord.js';
// const feedbackID = [];
export default {
data: new SlashCommandBuilder()
.setName('dm')
.setDescription('Replies with Pong!')
.addStringOption(option =>
option.setName('userid')
.setDescription('The user to who you want to send the message to.')
.setRequired(true))
.addStringOption(option =>
option.setName('message')
.setDescription('What do you want to tell them?')
.setRequired(true))
.addAttachmentOption(option =>
option.setName('image')
.setDescription('Optional attachment.')
.setRequired(false)),
category: 'owner',
ownerOnly: true,
async execute(interaction, args, client) {
/* Too lazy to implement that now (Watch it rest untouched for months)
async function uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
const uuid = uuidv4();
feedbackID[uuid] = args.message;
*/
await client.users.fetch(args.userid);
const user = client.users.resolve(args.userid);
if (!user) return interaction.reply('Not a valid ID');
const text = args.message;
const Embed = new EmbedBuilder()
.setTitle('You received a message from the developer!')
.setDescription(text)
.setFooter({ text: `If you wish to respond use the following command: ${interaction.prefix}feedback <message>` })
.setTimestamp();
user.send({ embeds: [Embed] });
return interaction.reply({ content: `DM sent to ${user.username} (${user.id})` });
/*
const Attachment = (message.attachments).array();
if (Attachment[0]) {
client.users.resolve(user).send(Embed, { files: [Attachment[0].url] })
.then(() => {
return interaction.reply(`DM sent to ${user.username}`);
})
.catch(() => {
return interaction.reply(`Could not send a DM to ${user.username}`);
});
}
else {
client.users.resolve(user).send(Embed)
.then(() => {
return interaction.reply(`DM sent to ${user.username}`);
})
.catch(() => {
return interaction.reply(`Could not send a DM to ${user.username}`);
});
}
*/
},
};

View file

@ -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.`);
},
};

View file

@ -1,22 +0,0 @@
import { SlashCommandBuilder } from 'discord.js';
export default {
data: new SlashCommandBuilder()
.setName('load')
.setDescription('load a command.')
.addStringOption(option =>
option.setName('file')
.setDescription('File location of the command.')
.setRequired(true)),
category: 'owner',
ownerOnly: true,
async execute(interaction, args, client) {
await interaction.deferReply();
let command = await import(`../../${args.file}`);
command = command.default;
client.commands.set(command.data.name, command);
return await interaction.editReply(`${command.data.name} has been loaded.`);
},
};

View file

@ -1,4 +1,5 @@
import { ButtonStyle, SlashCommandBuilder, ButtonBuilder, ActionRowBuilder } from 'discord.js'; import { SlashCommandBuilder } from '@discordjs/builders';
import { MessageButton, MessageActionRow } from 'discord.js';
import db from '../../models/index.js'; import db from '../../models/index.js';
const Blacklists = db.Blacklists; const Blacklists = db.Blacklists;
@ -18,67 +19,45 @@ export default {
option.setName('reason') option.setName('reason')
.setDescription('The reason of the blacklist.') .setDescription('The reason of the blacklist.')
.setRequired(false)), .setRequired(false)),
category: 'owner',
ownerOnly: true, ownerOnly: true,
async execute(interaction, args) { async execute(interaction) {
await interaction.deferReply({ ephemeral: true }); await interaction.deferReply({ ephemeral: true });
const client = interaction.client; const client = interaction.client;
const command = args.command; const command = interaction.options.getString('command');
const userid = args.userid; const userid = interaction.options.getString('userid');
const reason = args.reason ? args.reason : 'No reason has been specified.'; const reason = interaction.options.getString('reason');
const blacklist = await Blacklists.findOne({ where: { type:command, uid:userid } }); const blacklist = await Blacklists.findOne({ where: { type:command, uid:userid } });
if (!blacklist) { if (!blacklist) {
const body = { type:command, uid: userid, reason: reason }; const body = { type:command, uid: userid, reason: reason };
Blacklists.create(body); Blacklists.create(body);
if (command === 'guild') {
const guildid = userid;
await client.guilds.fetch(guildid);
const guild = client.guilds.resolve(guildid).name;
return interaction.editReply(`The guild ${guild} (${guildid}) has been blacklisted with the following reason \`${reason}\``);
}
else {
let user = userid; let user = userid;
await client.users.fetch(userid); if (command !== 'guild') {user = client.users.resolve(userid).tag;}
user = client.users.resolve(userid).username;
return interaction.editReply(`${user} has been blacklisted from ${command} with the following reason ${reason}`);
return interaction.editReply(`${user} (${userid}) has been blacklisted from ${command} with the following reason \`${reason}\``);
}
} }
else { else {
const row = new ActionRowBuilder() const row = new MessageActionRow()
.addComponents( .addComponents(
new ButtonBuilder() new MessageButton()
.setCustomId(`yes${interaction.user.id}${interaction.id}`) .setCustomId('yes')
.setLabel('Yes') .setLabel('Yes')
.setStyle(ButtonStyle.Primary), .setStyle('PRIMARY'),
) )
.addComponents( .addComponents(
new ButtonBuilder() new MessageButton()
.setCustomId(`no${interaction.user.id}${interaction.id}`) .setCustomId('no')
.setLabel('No') .setLabel('No')
.setStyle(ButtonStyle.Danger), .setStyle('DANGER'),
); );
await interaction.editReply({ content: 'This user is already blacklisted, do you want to unblacklist him?', ephemeral: true, components: [row] }); 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 (!interactionMenu.isButton) return;
}, interactionMenu.update({ components: [] });
}; if (interactionMenu.customId === 'yes') {
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 } }); Blacklists.destroy({ where: { type:command, uid:userid } });
return interaction.editReply(`The following ID have been unblacklisted from ${command}: ${userid}`); return interaction.editReply(`The following ID have been unblacklisted from ${command}: ${userid}`);
} }
@ -87,3 +66,5 @@ async function listenButton(client, interaction, command, userid, user = interac
} }
}); });
} }
},
};

View file

@ -1,50 +0,0 @@
import { SlashCommandBuilder } from 'discord.js';
import fs from 'node:fs';
export default {
data: new SlashCommandBuilder()
.setName('unload')
.setDescription('Unload a command and replace it with a placeholder')
.addStringOption(option =>
option.setName('commandname')
.setDescription('The command to unload.')
.setRequired(true))
.addStringOption(option =>
option.setName('placeholder')
.setDescription('The placeholder message you want for the command.'))
.addBooleanOption(option =>
option.setName('nofile')
.setDescription('Don\'t create the placeholder file')),
category: 'owner',
ownerOnly: true,
async execute(interaction, args, client) {
await interaction.deferReply();
if (!client.commands.has(args.commandname)) return await interaction.editReply('Command not found.');
if (!args.placeholder) args.placeholder = 'This command is unloaded, please check back later.';
if (!args.nofile) {
fs.writeFileSync(`./unloaded/${args.commandname}.js`, `
import { SlashCommandBuilder } from 'discord.js';
export default {
data: ${JSON.stringify(client.commands.get(args.commandname).data)},
category: '${client.commands.get(args.commandname).category}',
async execute(interaction) {
return interaction.reply('${args.placeholder.replace(/'/g, '\\\'')}');
},
};
`);
}
client.commands.delete(args.commandname);
if (!args.nofile) {
let command = await import(`../../unloaded/${args.commandname}.js`);
command = command.default;
client.commands.set(args.commandname, command);
}
return await interaction.editReply(`${args.commandname} has been unloaded.`);
},
};

View file

@ -1,90 +0,0 @@
/* eslint-disable no-case-declarations */
import { SlashCommandBuilder } from 'discord.js';
const { ownerId } = process.env;
export default {
data: new SlashCommandBuilder()
.setName('s')
.setDescription('What could this be 🤫')
.addStringOption(option =>
option.setName('something')
.setDescription('🤫')
.setRequired(true))
.addStringOption(option =>
option.setName('somethingelse')
.setDescription('🤫')
.setRequired(false)),
category: 'secret',
async execute(interaction, args, client) {
const command = args.something;
switch (command) {
case 'levertowned':
return interaction.reply('Hello buddy bro <:youngtroll:488559163832795136> <@434762632004894746>');
case 'owned':
const epicMessage = ['You cheated not ONLY the GAME, BUT yourself. You didn\'t GROW. You didn\'t IMPROVE. You TOOK a SHORTCUT and gained NOTHING. You EXPERIENCED a HOLLOW victory. NOTHING WAS risked and NOTHING WAS gained. It\'s SAD that you don\'t KNOW the DIFFERENCE.', 'TROLOLOLO OWNED EPIC STYLE', 'Owned noob', 'HAHA BRO YOU JUST GOT OOOOOOOOWNED HAHAHAHAHHAHA NOOOB NOOOOB NOOOB OWNED NOOB <:youngtroll:488559163832795136>', '<a:op:516341492982218756> op op op owned epic style <a:op:516341492982218756>', 'HAHAHA BRO YOU HAVE BEEN OWNED TROLL BRO STYLE', 'OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED YOU JUST GOT OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED YOU JUST GOT OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED YOU JUST GOT OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED YOU JUST GOT OWNED', 'OWNED\nFUCKING OWNED', 'OWNED!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!', 'You just got owned <a:troll:525724709833277442>', 'you just been....epicly trolled <:trole:241942993022615552>', 'I hope you have a nice day, just a simple reminder that I love you and I think you\'re pretty epic!!!!'];
const ownedMessage = epicMessage[Math.floor(Math.random() * epicMessage.length)];
await interaction.guild.members.fetch();
let owned = args.somethingelse ? interaction.guild.members.cache.find(u => u.user.username.toLowerCase().includes(args.somethingelse.toLowerCase())) : null;
if (interaction.isMessage) {
if (interaction.mentions.members.first()) {
console.log('test');
owned = interaction.mentions.members.first();
}
}
if (args.somethingelse) {
if (owned.id === client.user.id) {
return interaction.reply('You really thought you could own me?, pathetic...');
}
else if (owned.id === ownerId) {
return interaction.reply('You really thought you could own him?, pathetic...');
}
else if (owned.id === '286054184623538177' || owned.id === '172112210863194113') {
owned = interaction.user;
}
if (ownedMessage === epicMessage[0]) {
return interaction.reply(ownedMessage);
}
return interaction.reply(`${owned}, ${ownedMessage}`);
}
else {
return interaction.reply(ownedMessage);
}
case 'fartpiss':
if (interaction.guild.id != '240843640375607296') {
return;
}
await interaction.guild.members.fetch();
const member = args.somethingelse ? interaction.guild.members.cache.find(u => u.user.username.toLowerCase().includes(args.somethingelse.toLowerCase())) : null;
if (member) {
return await member.setNickname('fart piss')
.then(() => interaction.reply(`sucessfully fart pissed on ${member.user.username} <:youngtroll:488559163832795136>`))
.catch((error) => {
if (process.env.NODE_ENV === 'development') console.error(error);
interaction.reply(`Sorry i could not fart piss on ${member.user.username} :(`);
});
}
else {
return await interaction.member.setNickname('fart piss')
.then(() => interaction.reply('sucessfully fart pissed on you <:youngtroll:488559163832795136>'))
.catch((error) => {
if (process.env.NODE_ENV === 'development') console.error(error);
interaction.reply('Sorry i could not fart piss on you :(');
});
}
default:
break;
}
},
};

View file

@ -1,31 +1,31 @@
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from '@discordjs/builders';
import { EmbedBuilder } from 'discord.js'; import { MessageEmbed } from 'discord.js';
import { execFile } from 'node:child_process'; import { exec } from 'node:child_process';
import db from '../../models/index.js'; import db from '../../models/index.js';
const donator = db.donator; const donator = db.donator;
const { ownerId, uptimePage } = process.env; import dotenv from 'dotenv';
dotenv.config();
const { ownerId } = process.env;
export default { export default {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('about') .setName('about')
.setDescription('About me (The bot)'), .setDescription('About me (The bot)'),
category: 'utility',
async execute(interaction) { async execute(interaction) {
const Donator = await donator.findAll({ order: ['id'] }); const Donator = await donator.findAll({ order: ['id'] });
const client = interaction.client; const client = interaction.client;
const tina = await client.users.fetch('336492042299637771'); 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); 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)' let description = `This bot is made using [discord.js](https://github.com/discordjs/discord.js)\nThanks to ${tina.tag} (336492042299637771) for inspiring me for making this bot!\n\nThe people who donated for the bot <3\n`;
+ '\nFor a better experience use the slash commands!\n\nThe people who donated for the bot <3\n';
if (Donator[0]) { if (Donator[0]) {
for (let i = 0; i < Donator.length; i++) { for (let i = 0; i < Donator.length; i++) {
const user = await client.users.fetch(Donator[i].get('userID').toString()); const user = await client.users.fetch(Donator[i].get('userID').toString());
if (user !== null) { if (user !== null) {
description += `**${user.username} (${user.id}) | ${Donator[i].get('comment')}**\n`; description += `**${user.tag} (${user.id}) | ${Donator[i].get('comment')}**\n`;
} }
else { else {
description += `**A user of discord (${user.id}) | ${Donator[i].get('comment')} (This user no longer share a server with the bot)**\n`; 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,20 @@ export default {
description += 'No one :(\n'; description += 'No one :(\n';
} }
description += `\nThanks to ${tina.username} (336492042299637771) for inspiring me for making this bot!`;
// description += '\nThanks to Jetbrains for providing their IDE!'; // 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() const aboutEmbed = new MessageEmbed()
.setColor(interaction.member ? interaction.member.displayHexColor : 'Navy') .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') .setTitle('About me')
.setDescription(description) .setDescription(description)
.addFields( .addField('Current commit', stdout)
{ name: 'Current commit', value: stdout }, .addField('Current maintainer: ', `${maintainer.tag} (${ownerId})`)
{ name: 'Current maintainer', value: `${maintainer.username} (${ownerId})` }, .addField('Gitea (Main)', 'https://git.namejeff.xyz/Supositware/Haha-Yes', true)
{ name: 'Gitea (Main)', value: 'https://git.namejeff.xyz/Supositware/Haha-Yes', inline: true }, .addField('Github (Mirror)', 'https://github.com/Supositware/Haha-yes', true)
{ name: 'Github (Mirror)', value: 'https://github.com/Supositware/Haha-yes', inline: true }, .addField('Privacy Policy', 'https://libtar.de/discordprivacy.txt')
{ name: 'Privacy Policy', value: 'https://libtar.de/discordprivacy.txt', inline: true }, .setFooter({ text: `Original bot made by ${owner.tag} (267065637183029248)` });
{ name: 'Status page', value: uptimePage.toString(), inline: true },
)
.setFooter({ text: `Original bot made by ${creator.username} (267065637183029248)` });
interaction.reply({ embeds: [aboutEmbed] }); interaction.reply({ embeds: [aboutEmbed] });
}); });

View file

@ -1,99 +0,0 @@
import { SlashCommandBuilder, EmbedBuilder } from 'discord.js';
import utils from '../../utils/videos.js';
import fs from 'node:fs';
import os from 'node:os';
const { ytpChannelId } = process.env;
export default {
data: new SlashCommandBuilder()
.setName('addytp')
.setDescription('Add a video to the pool of ytps. You can add 5 per day.')
.addStringOption(option =>
option.setName('url')
.setDescription('URL of the video you want to add.')
.setRequired(true)),
category: 'utility',
ratelimit: 5,
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 });
}
await interaction.deferReply({ ephemeral: true });
utils.downloadVideo(url, interaction.id, 'bestvideo[height<=?480]+bestaudio/best')
.then(async () => {
const file = fs.readdirSync(os.tmpdir()).filter(fn => fn.startsWith(interaction.id));
const output = `${os.tmpdir()}/${file}`;
const fileStat = fs.statSync(output);
const fileSize = fileStat.size / 1000000.0;
if (fileSize > 50) {
// await interaction.deleteReply();
await interaction.editReply({ content: '❌ Uh oh! The video is too big!', ephemeral: true });
}
else {
// CopyFile instead of rename in case you have /tmp and the asset folder on different a disk.
fs.copyFileSync(output, `./asset/ytp/userVid/${file}`);
const mp4 = [];
fs.readdirSync('./asset/ytp/userVid/').forEach(f => {
if (f.endsWith('mp4')) {
mp4.push(f);
}
});
// (Hopefully) limit video to 2k
if (mp4.length > 2000) {
const f = mp4.sort((a, b) => {
const time1 = fs.statSync(`./asset/ytp/userVid/${b}`).ctime;
const time2 = fs.statSync(`./asset/ytp/userVid/${a}`).ctime;
if (time1 < time2) return 1;
if (time1 > time2) return -1;
return 0;
}).slice(0, 1);
fs.unlinkSync(`./asset/ytp/userVid/${f[0]}`);
}
interaction.editReply({ content: `Video successfully added to the pool! There is now ${mp4.length} videos`, ephemeral: true });
const Embed = new EmbedBuilder()
.setAuthor({ name: interaction.user.username, iconURL: interaction.user.displayAvatarURL() })
.addFields([
{ name: 'Channel ID', value: interaction.channel.id.toString(), inline: true },
{ name: 'Message ID', value: interaction.id.toString(), 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}` },
]);
}
else {
Embed.addFields([
{ name: 'Message link', value: `https://discord.com/channels/@me/${interaction.channel.id}/${interaction.id}` },
]);
}
const channel = interaction.client.channels.resolve(ytpChannelId);
// Send as 2 separate message otherwise the url won't get embedded.
channel.send({ content: url });
return channel.send({ embeds: [Embed] });
}
});
},
};

View file

@ -1,49 +0,0 @@
import { EmbedBuilder, SlashCommandBuilder } from 'discord.js';
export default {
data: new SlashCommandBuilder()
.setName('avatar')
.setDescription('Show user avatar')
.addMentionableOption(option =>
option.setName('member')
.setDescription('Who do you want to fake?')
.setRequired(false)),
category: 'utility',
async execute(interaction, args) {
const avatarEmbed = new EmbedBuilder()
.setColor(interaction.member ? interaction.member.displayHexColor : 'Navy')
.setTitle('Avatar');
if (!args.member) {
const format = interaction.user.displayAvatarURL({ dynamic: true }).substr(interaction.user.displayAvatarURL({ dynamic: true }).length - 3);
if (format == 'gif') {
avatarEmbed.setAuthor({ name: interaction.user.username });
avatarEmbed.setDescription(`[gif](${interaction.user.displayAvatarURL({ format: 'gif', size: 2048 })})`);
avatarEmbed.setImage(interaction.user.displayAvatarURL({ format: 'gif', size: 2048 }));
}
else {
avatarEmbed.setAuthor({ name: interaction.user.username });
avatarEmbed.setDescription(`[png](${interaction.user.displayAvatarURL({ format: 'png', size: 2048 })}) | [jpeg](${interaction.user.displayAvatarURL({ format: 'jpg', size: 2048 })}) | [webp](${interaction.user.displayAvatarURL({ format: 'webp', size: 2048 })})`);
avatarEmbed.setImage(interaction.user.displayAvatarURL({ format: 'png', size: 2048 }));
}
return interaction.reply({ embeds: [avatarEmbed] });
}
else {
await interaction.guild.members.fetch();
const format = args.member.displayAvatarURL({ dynamic: true }).substr(args.member.displayAvatarURL({ dynamic: true }).length - 3);
if (format == 'gif') {
avatarEmbed.setAuthor({ name: args.member.user.username });
avatarEmbed.setDescription(`[gif](${args.member.displayAvatarURL({ format: 'gif', size: 2048 })})`);
avatarEmbed.setImage(args.member.displayAvatarURL({ format: 'gif', size: 2048 }));
}
else {
avatarEmbed.setAuthor({ name: args.member.user.username });
avatarEmbed.setDescription(`[png](${args.member.displayAvatarURL({ format: 'png', size: 2048 })}) | [jpeg](${args.member.displayAvatarURL({ format: 'jpg', size: 2048 })}) | [webp](${args.member.displayAvatarURL({ format: 'webp', size: 2048 })})`);
avatarEmbed.setImage(args.member.displayAvatarURL({ format: 'png', size: 2048 }));
}
return interaction.reply({ embeds: [avatarEmbed] });
}
},
};

View file

@ -1,20 +1,19 @@
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from '@discordjs/builders';
import { EmbedBuilder } from 'discord.js'; import { MessageEmbed } from 'discord.js';
import donations from '../../json/donations.json' with {type: 'json'}; import donations from '../../json/donations.json' assert {type: 'json'};
export default { export default {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('donate') .setName('donate')
.setDescription('Show donation link for the bot.'), .setDescription('Show donation link for the bot.'),
category: 'utility',
async execute(interaction) { async execute(interaction) {
let desc = 'If you decide to donate, please do /feedback to let the owner know about it so he can put you in the about and donator command.'; let desc = 'If you decide to donate, please do /feedback to let the owner know about it so he can put you in the about and donator command.';
donations.forEach(m => { donations.forEach(m => {
desc += `\n${m}`; desc += `\n${m}`;
}); });
const Embed = new EmbedBuilder() const Embed = new MessageEmbed()
.setColor(interaction.member ? interaction.member.displayHexColor : 'Navy') .setColor(interaction.member ? interaction.member.displayHexColor : 'NAVY')
.setTitle('Donation link') .setTitle('Donation link')
.setDescription(desc); .setDescription(desc);

View file

@ -1,4 +1,4 @@
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from '@discordjs/builders';
import db from '../../models/index.js'; import db from '../../models/index.js';
const donator = db.donator; const donator = db.donator;
@ -6,7 +6,6 @@ export default {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('donator') .setName('donator')
.setDescription('All the people who donated for this bot <3'), .setDescription('All the people who donated for this bot <3'),
category: 'utility',
async execute(interaction) { async execute(interaction) {
await interaction.deferReply(); await interaction.deferReply();
const client = interaction.client; const client = interaction.client;
@ -17,7 +16,7 @@ export default {
if (Donator[0]) { if (Donator[0]) {
for (let i = 0; i < Donator.length; i++) { for (let i = 0; i < Donator.length; i++) {
const user = await client.users.fetch(Donator[i].get('userID').toString()); 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`;} else {donatorMessage += `**A user of discord (${user.id}) | ${Donator[i].get('comment')} (This user no longer share a server with the bot)**\n`;}
} }

View file

@ -1,17 +1,10 @@
import { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, StringSelectMenuBuilder } from 'discord.js'; import { SlashCommandBuilder } from '@discordjs/builders';
import { execFile } from 'node:child_process'; import { MessageEmbed, MessageActionRow, MessageSelectMenu } from 'discord.js';
import { exec } from 'node:child_process';
import fs from 'node:fs'; import fs from 'node:fs';
import os from 'node:os'; import os from 'node:os';
import utils from '../../utils/videos.js'; import utils from '../../utils/videos.js';
let client;
let maxFileSize;
let { ytdlpMaxResolution } = process.env;
const { proxy } = process.env;
// Convert to number as process.env is always a string
ytdlpMaxResolution = Number(ytdlpMaxResolution);
export default { export default {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('download') .setName('download')
@ -26,47 +19,21 @@ export default {
.setRequired(false)) .setRequired(false))
.addBooleanOption(option => .addBooleanOption(option =>
option.setName('compress') option.setName('compress')
.setDescription('Compress the video.') .setDescription('Compress the video?')
.setRequired(false))
.addBooleanOption(option =>
option.setName('autocrop')
.setDescription('Autocrop borders on videos. Ignored when using compress option.')
.setRequired(false))
.addBooleanOption(option =>
option.setName('description')
.setDescription('Include the video description.')
.setRequired(false)), .setRequired(false)),
category: 'utility',
alias: ['dl'],
integration_types: [0, 1],
async execute(interaction, args, c) {
client = c;
const url = args.url;
const format = args.format;
maxFileSize = await utils.getMaxFileSize(interaction.guild);
interaction.doCompress = args.compress;
interaction.doAutocrop = args.autocrop;
async execute(interaction) {
await interaction.deferReply({ ephemeral: false }); await interaction.deferReply({ ephemeral: false });
const url = interaction.options.getString('url');
if (interaction.isMessage) {
interaction.delete();
}
if (!await utils.stringIsAValidurl(url)) { if (!await utils.stringIsAValidurl(url)) {
console.error(`Not a url!!! ${url}`); console.error(`Not a url!!! ${url}`);
return interaction.editReply({ content: '❌ This does not look like a valid url!', ephemeral: true }); return interaction.editReply({ content: '❌ This does not look like a valid url!', ephemeral: true });
} }
if (format) { if (interaction.options.getBoolean('format')) {
let qualitys = await new Promise((resolve, reject) => { let qualitys = await new Promise((resolve, reject) => {
const options = [url, '--print', '%()j']; exec(`./bin/yt-dlp "${url}" --print "%()j"`, (err, stdout, stderr) => {
if (proxy) {
options.push('--proxy');
options.push(proxy);
};
execFile('./bin/yt-dlp', options, (err, stdout, stderr) => {
if (err) { if (err) {
reject(stderr); reject(stderr);
} }
@ -76,12 +43,11 @@ export default {
resolve(stdout); resolve(stdout);
}); });
}); });
qualitys = JSON.parse(qualitys); qualitys = JSON.parse(qualitys);
const options = []; const options = [];
qualitys.formats.forEach(f => { qualitys.formats.forEach(f => {
if (f.format.includes('storyboard')) return;
options.push({ options.push({
label: f.resolution ? f.resolution : 'Unknown format', label: f.resolution ? f.resolution : 'Unknown format',
description: `${f.format} V: ${f.vcodec} A: ${f.acodec}`, description: `${f.format} V: ${f.vcodec} A: ${f.acodec}`,
@ -105,10 +71,10 @@ export default {
options.reverse(); options.reverse();
} }
const row = new ActionRowBuilder() const row = new MessageActionRow()
.addComponents( .addComponents(
new StringSelectMenuBuilder() new MessageSelectMenu()
.setCustomId(`downloadQuality${interaction.user.id}${interaction.id}`) .setCustomId('downloadQuality')
.setPlaceholder('Nothing selected') .setPlaceholder('Nothing selected')
.setMinValues(1) .setMinValues(1)
.setMaxValues(2) .setMaxValues(2)
@ -118,40 +84,27 @@ export default {
await interaction.deleteReply(); await interaction.deleteReply();
await interaction.followUp({ content: 'Which quality do you want?', ephemeral: true, components: [row] }); await interaction.followUp({ content: 'Which quality do you want?', ephemeral: true, components: [row] });
client.on('interactionCreate', async (interactionMenu) => { interaction.client.once('interactionCreate', async (interactionMenu) => {
if (interaction.user !== interactionMenu.user) return;
if (!interactionMenu.isSelectMenu()) return; if (!interactionMenu.isSelectMenu()) return;
if (interactionMenu.customId === `downloadQuality${interaction.user.id}${interaction.id}`) { if (interactionMenu.customId === 'downloadQuality') {
await interactionMenu.deferReply({ ephemeral: false }); await interactionMenu.deferReply({ ephemeral: false });
download(url, interactionMenu, interaction);
await checkSize(url, interactionMenu.values[0], args, interaction);
return download(url, interactionMenu, interaction, undefined, true);
} }
}); });
return; return;
} }
const newFormat = await checkSize(url, undefined, args, interaction); download(url, interaction);
return download(url, interaction, interaction, newFormat, args.description);
}, },
}; };
async function download(url, interaction, originalInteraction, format = undefined, description = false) { async function download(url, interaction, originalInteraction) {
let embedColour = 'Navy'; let format = 'bestvideo*+bestaudio/best';
if (interaction.member) { const Embed = new MessageEmbed()
if (interaction.member.displayHexColor) { .setColor(interaction.member ? interaction.member.displayHexColor : 'NAVY')
embedColour = interaction.member.displayHexColor; .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!` });
}
const Embed = new EmbedBuilder()
.setColor(embedColour)
.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) { if (interaction.customId === 'downloadQuality') {
Embed.setDescription(await getVideoDescription(url));
}
if (interaction.customId === `downloadQuality${interaction.user.id}${originalInteraction.id}` && !format) {
format = interaction.values[0]; format = interaction.values[0];
if (interaction.values[1]) format += '+' + interaction.values[1]; if (interaction.values[1]) format += '+' + interaction.values[1];
} }
@ -159,11 +112,13 @@ async function download(url, interaction, originalInteraction, format = undefine
utils.downloadVideo(url, interaction.id, format) utils.downloadVideo(url, interaction.id, format)
.then(async () => { .then(async () => {
const file = fs.readdirSync(os.tmpdir()).filter(fn => fn.startsWith(interaction.id)); const file = fs.readdirSync(os.tmpdir()).filter(fn => fn.startsWith(interaction.id));
let output = `${os.tmpdir()}/${file}`; const output = `${os.tmpdir()}/${file}`;
const fileStat = fs.statSync(output);
const fileSize = fileStat.size / 1000000.0;
const compressInteraction = originalInteraction ? originalInteraction : interaction; const compressInteraction = originalInteraction ? originalInteraction : interaction;
if (compressInteraction.doCompress) { if (compressInteraction.options.getBoolean('compress')) {
const presets = [ 'Social 25 MB 5 Minutes 360p60', 'Social 25 MB 2 Minutes 540p60', 'Social 25 MB 1 Minute 720p60', 'Social 25 MB 30 Seconds 1080p60' ]; const presets = [ 'Discord Tiny 5 Minutes 240p30', 'Discord Small 2 Minutes 360p30', 'Discord Nitro Small 10-20 Minutes 480p30', 'Discord Nitro Medium 5-10 Minutes 720p30', 'Discord Nitro Large 3-6 Minutes 1080p30' ];
const options = []; const options = [];
presets.forEach(p => { presets.forEach(p => {
@ -173,88 +128,41 @@ async function download(url, interaction, originalInteraction, format = undefine
}); });
}); });
const row = new ActionRowBuilder() const row = new MessageActionRow()
.addComponents( .addComponents(
new StringSelectMenuBuilder() new MessageSelectMenu()
.setCustomId(`preset${interaction.user.id}${interaction.id}`) .setCustomId('preset')
.setPlaceholder('Nothing selected') .setPlaceholder('Nothing selected')
.addOptions(options), .addOptions(options),
); );
await interaction.deleteReply(); await interaction.deleteReply();
await interaction.followUp({ content: 'Which compression preset do you want?', ephemeral: true, components: [row] }); await interaction.followUp({ content: 'Which compression preset do you want?', ephemeral: true, components: [row] });
client.on('interactionCreate', async (interactionMenu) => { interaction.client.once('interactionCreate', async (interactionMenu) => {
if (interaction.user !== interactionMenu.user) return;
if (!interactionMenu.isSelectMenu()) return; if (!interactionMenu.isSelectMenu()) return;
if (interactionMenu.customId === `preset${interaction.user.id}${interaction.id}`) { if (interactionMenu.customId === 'preset') {
await interactionMenu.deferReply({ ephemeral: false }); await interactionMenu.deferReply({ ephemeral: false });
compress(file, interactionMenu, Embed); compress(file, interactionMenu, Embed);
if (interaction.isMessage) {
interaction.deleteReply();
interaction.cleanUp();
}
} }
}); });
return; return;
} }
// If the video format is not one compatible with Discord, reencode it unless autocrop is choosen in which case it gets reencoded anyway.
if (!interaction.doAutocrop) {
const bannedFormats = ['av1'];
const codec = await utils.getVideoCodec(output);
if (bannedFormats.includes(codec)) {
const oldOutput = output;
output = `${os.tmpdir()}/264${file}`;
await utils.ffmpeg(['-i', oldOutput, '-vcodec', 'libx264', '-acodec', 'aac', output]);
}
}
else if (interaction.doAutocrop && !compressInteraction.doCompress) {
const oldOutput = output;
output = `${os.tmpdir()}/autocrop${file}`;
await utils.autoCrop(oldOutput, 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);
}
if (fileSize > 100) { if (fileSize > 100) {
await interaction.deleteReply(); await interaction.deleteReply();
await interaction.followUp('Uh oh! The video you tried to download is too big!', { ephemeral: true }); 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) const fileurl = await utils.upload(output)
.catch(err => { .catch(err => {
console.error(err); console.error(err);
}); });
await interaction.editReply({ content: 'File was bigger than 8 mb. It has been uploaded to an external site.', embeds: [Embed], ephemeral: false });
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 }); await interaction.followUp({ content: fileurl, ephemeral: false });
} }
}
else if (interaction.isMessage && message) {
await message.reply({ embeds: [Embed], files: [output] });
}
else { else {
await interaction.editReply({ embeds: [Embed], files: [output], ephemeral: false }); await interaction.editReply({ embeds: [Embed], files: [output], ephemeral: false });
} }
if (interaction.isMessage) {
interaction.deleteReply();
interaction.cleanUp();
}
}) })
.catch(async err => { .catch(async err => {
console.error(err); console.error(err);
@ -266,69 +174,16 @@ async function download(url, interaction, originalInteraction, format = undefine
async function compress(input, interaction, embed) { async function compress(input, interaction, embed) {
const output = `compressed${input}.mp4`; const output = `compressed${input}.mp4`;
// Delete the file as it apparently don't overwrite?
if (fs.existsSync(output)) {
fs.rmSync(output);
}
utils.compressVideo(`${os.tmpdir()}/${input}`, output, interaction.values[0]) utils.compressVideo(`${os.tmpdir()}/${input}`, output, interaction.values[0])
.then(async () => { .then(async () => {
const fileStat = fs.statSync(`${os.tmpdir()}/${output}`); const fileStat = fs.statSync(`${os.tmpdir()}/${output}`);
const fileSize = fileStat.size / 1000000.0; 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 > 8) {
await interaction.editReply({ content: 'File was bigger than 8 mb. but due to the compression it is not being uploaded externally.', ephemeral: true });
if (fileSize > maxFileSize) {
await interaction.editReply({ content: `File was bigger than ${maxFileSize} mb. It has been uploaded to an external site.`, ephemeral: false });
} }
else { else {
await interaction.editReply({ embeds: [embed], files: [`${os.tmpdir()}/${output}`], ephemeral: false }); 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) => {
const options = [urlArg, '--no-warnings', '-O', '%(description)s'];
if (proxy) {
options.push('--proxy');
options.push(proxy);
};
execFile('./bin/yt-dlp', options, (err, stdout, stderr) => {
if (err) {
reject(stderr);
}
if (stderr) {
console.error(stderr);
}
resolve(stdout.slice(0, 240));
});
});
}

View file

@ -1,5 +1,5 @@
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from '@discordjs/builders';
import { EmbedBuilder } from 'discord.js'; import { MessageEmbed } from 'discord.js';
const { feedbackChannelId } = process.env; const { feedbackChannelId } = process.env;
@ -10,19 +10,14 @@ export default {
.addStringOption(option => .addStringOption(option =>
option.setName('feedback') option.setName('feedback')
.setDescription('The message you want to send me.') .setDescription('The message you want to send me.')
.setRequired(true)) .setRequired(true)),
.addAttachmentOption(option => async execute(interaction) {
option.setName('image') const Embed = new MessageEmbed()
.setDescription('Optional attachment.') .setAuthor({ name: `${interaction.user.tag} (${interaction.user.id})`, iconURL: interaction.user.avatarURL() })
.setRequired(false)),
category: 'utility',
async execute(interaction, args) {
const Embed = new EmbedBuilder()
.setAuthor({ name: `${interaction.user.username} (${interaction.user.id})`, iconURL: interaction.user.avatarURL() })
.setTimestamp(); .setTimestamp();
if (interaction.guild) Embed.addFields({ name: 'Guild', value: `${interaction.guild.name} (${interaction.guild.id})`, inline: true }); if (interaction.guild) Embed.addField('Guild', `${interaction.guild.name} (${interaction.guild.id})`, true);
Embed.addFields({ name: 'Feedback', value: args.feedback, inline: true }); Embed.addField('Feedback', interaction.options.getString('feedback'));
// Don't let new account use this command to prevent spam // Don't let new account use this command to prevent spam
const date = new Date(); const date = new Date();
@ -31,12 +26,7 @@ export default {
} }
const channel = interaction.client.channels.resolve(feedbackChannelId); const channel = interaction.client.channels.resolve(feedbackChannelId);
if (args.image) {
channel.send({ embeds: [Embed], files: [args.image] });
}
else {
channel.send({ embeds: [Embed] }); channel.send({ embeds: [Embed] });
}
await interaction.reply({ content: 'Your feedback has been sent! Don\'t forget to have dm open if you want to get an answer from the dev!', ephemeral: true }); await interaction.reply({ content: 'Your feedback has been sent! Don\'t forget to have dm open if you want to get an answer from the dev!', ephemeral: true });
}, },
}; };

View file

@ -1,164 +0,0 @@
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(',');
export default {
data: new SlashCommandBuilder()
.setName('help')
.setDescription('Displays a list of commands or information about a command.')
.addStringOption(option =>
option.setName('command')
.setDescription('The command you want more details about.')),
category: 'utility',
async execute(interaction, args, client) {
if (args.command) {
const command = client.commands.get(args.command);
if (!command) return interaction.reply(`Did not found any command named \`\`${args.command}\`\`. Please make sure it is a valid command and not an alias.`);
const description = Object.assign({
content: 'No description available.',
usage: '',
examples: [],
fields: [],
}, command.data);
const usage = command.data.options.map(cmd => {
let type = 'String';
const constructorName = cmd.constructor.name.toLowerCase();
if (constructorName.includes('boolean')) {
if (interaction.isMessage) {
type = `--${cmd.name}`;
}
else {
type = 'True/False';
}
}
else if (constructorName.includes('mentionable')) {
type = 'User';
}
else if (constructorName.includes('attachment')) {
type = 'Attachment';
}
return `[${cmd.name}: ${type}]`;
});
let p = '/';
if (interaction.isMessage) {
p = prefixs[0];
}
const embed = new EmbedBuilder()
.setColor(interaction.member ? interaction.member.displayHexColor : 'Navy')
.setTitle(`\`${p}${command.data.name} ${usage.join(' ')}\``)
.addFields(
{ name: 'Description', value: description.description },
)
.setFooter({ text: `All the available prefix: ${prefixs.join(' | ')}` });
for (const field of description.fields) embed.addFields({ name: field.name, value: field.value });
if (description.examples.length) {
const text = `${prefixs[0]}${command.alias[0]}`;
embed.addFields({ name: 'Examples', value: `\`${text} ${description.examples.join(`\`\n\`${text} `)}\``, inline: true });
}
else {
const example = command.data.options.map(cmd => {
let string = '"lorem ipsum"';
const constructorName = cmd.constructor.name.toLowerCase();
if (constructorName.includes('boolean')) {
if (interaction.isMessage) {
string = `--${cmd.name}`;
}
else {
string = 'True/False';
}
}
else if (constructorName.includes('mentionable')) {
string = `@${interaction.user.username}`;
}
else if (constructorName.includes('attachment')) {
string = 'Attachment';
}
if (!interaction.isMessage) {
string = `\`\`${cmd.name}:${string}\`\``;
}
return string;
});
embed.addFields({ name: 'Example', value: `${p}${command.data.name} ${example.join(' ')}`, inline: true });
}
if (command.alias) {
if (command.alias.length >= 1) {
embed.addFields({ name: 'Aliases', value: `\`${command.alias.join('` `')}\``, inline: true });
}
}
if (command.userPermissions) {
const perm = [];
command.userPermissions.forEach(permission => {
perm.push(new PermissionsBitField(permission).toArray());
});
embed.addFields({ name: 'User permission', value: `\`${perm.join('` `')}\``, inline: true });
}
if (command.clientPermissions) {
const perm = [];
command.clientPermissions.forEach(permission => {
perm.push(new PermissionsBitField(permission).toArray());
});
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`);
return interaction.reply({ embeds: [embed], files: [file] });
}
return interaction.reply({ embeds: [embed] });
}
else {
const embed = new EmbedBuilder()
.setColor(interaction.member ? interaction.member.displayHexColor : 'Navy')
.addFields({ name: 'Command List', value: `This is a list of commands.\nTo view details for a command, do \`${prefixs[0]}help <command>\`.` })
.setFooter({ text: `All the available prefix: ${prefixs.join('| ')}` });
const object = { };
for (const command of client.commands.values()) {
if (command.category === 'secret') continue;
if (interaction.user.id !== ownerId && command.category === 'owner') continue;
if (object[command.category]) {
object[command.category].push(command.data.name);
}
else {
object[command.category] = [ command.data.name ];
}
}
for (const category in object) {
const title = {
fun: '🎉\u2000Fun',
utility: '🔩\u2000Utility',
admin: '⚡\u2000Admin',
owner: '🛠️\u2000Owner',
voice: '🗣️\u2000Voice',
AI: '🦾\u2000AI',
}[category];
embed.addFields({ name: title, value: `\`${object[category].join('` `')}\`` });
}
return interaction.reply({ embeds: [embed] });
}
},
};

View file

@ -1,27 +0,0 @@
import { SlashCommandBuilder } from 'discord.js';
export default {
data: new SlashCommandBuilder()
.setName('invite')
.setDescription('Generate invite link for the bot or another')
.addMentionableOption(option =>
option.setName('bot')
.setDescription('The bot you want to make an invite link for.')
.setRequired(false)),
category: 'utility',
integration_types: [0, 1],
async execute(interaction, args, client) {
if (args.bot) {
if (args.bot.user.bot) {
return interaction.reply(`You can add the bot you mentioned with this link: https://discordapp.com/oauth2/authorize?client_id=${args.bot.id}&permissions=2684406848&scope=bot%20applications.commands\n\`Note: The invite will not work if the bot is not public\``);
}
else {
return interaction.reply('I\'m sorry but the user you mentioned is not a bot!');
}
}
else {
return interaction.reply(`
You can add me for your server from here: https://discord.com/oauth2/authorize?client_id=${client.user.id}&permissions=2684406848&scope=bot%20applications.commands` +
`\nIf you want to use my commands no matter the server you can install me as a user applications from here: https://discord.com/oauth2/authorize?client_id=${client.user.id}`);
}
},
};

View file

@ -1,68 +0,0 @@
import { SlashCommandBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js';
import db from '../../models/index.js';
export default {
data: new SlashCommandBuilder()
.setName('optout')
.setDescription('Opt out of the non commands features and arguments logging (for debugging purposes)'),
category: 'utility',
integration_types: [0, 1],
async execute(interaction, args, client) {
const isOptOut = await db.optout.findOne({ where: { userID: interaction.user.id } });
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 });
}
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,
},
);
},
};
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 });
}
});
}

View file

@ -1,22 +1,10 @@
import { SlashCommandBuilder, ButtonBuilder, ActionRowBuilder, ButtonStyle } from 'discord.js'; import { SlashCommandBuilder } from '@discordjs/builders';
const { uptimePage } = process.env;
export default { export default {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('ping') .setName('ping')
.setDescription('Replies with Pong!'), .setDescription('Replies with Pong!'),
category: 'utility',
integration_types: [0, 1],
async execute(interaction) { async execute(interaction) {
const row = new ActionRowBuilder() await interaction.reply(`Pong! \`${Math.round(interaction.client.ws.ping)} ms\``);
.addComponents(
new ButtonBuilder()
.setLabel('Status page')
.setURL(uptimePage)
.setStyle(ButtonStyle.Link),
);
await interaction.reply({ content: `Pong! \`${Math.round(interaction.client.ws.ping)} ms\``, components: [row] });
}, },
}; };

View file

@ -1,33 +0,0 @@
import { SlashCommandBuilder, EmbedBuilder } from 'discord.js';
export default {
data: new SlashCommandBuilder()
.setName('serverinfo')
.setDescription('Show info about the server'),
category: 'utility',
alias: ['server'],
async execute(interaction, args, client) {
await interaction.guild.members.fetch();
const botCount = interaction.guild.members.cache.filter(member => member.user.bot).size;
const guildOwner = await client.users.fetch(interaction.guild.ownerId);
const addEmbed = new EmbedBuilder()
.setColor(interaction.member ? interaction.member.displayHexColor : 'NAVY')
.setTitle(interaction.guild.name)
.setThumbnail(interaction.guild.iconURL())
.addFields(
{ name: 'Number of users', value: (interaction.guild.memberCount - botCount).toString(), inline: true },
{ name: 'Number of bots', value: botCount.toString(), inline: true },
{ name: 'Total number of members', value: interaction.guild.memberCount.toString(), inline: true },
{ name: 'Number of channels', value: interaction.guild.channels.cache.size.toString(), inline: true },
{ name: '', value:'' },
{ name: 'Date when guild created', value: interaction.guild.createdAt.toString(), inline: true },
{ name: 'Owner', value: guildOwner.toString(), inline: true },
)
.setTimestamp();
interaction.reply({ embeds: [addEmbed] });
},
};

View file

@ -1,12 +1,11 @@
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from '@discordjs/builders';
import { EmbedBuilder, version } from 'discord.js'; import { MessageEmbed, version } from 'discord.js';
import os from 'node:os'; import os from 'node:os';
export default { export default {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
.setName('stats') .setName('stats')
.setDescription('Show some stats about the bot'), .setDescription('Show some stats about the bot'),
category: 'utility',
async execute(interaction) { async execute(interaction) {
const client = interaction.client; const client = interaction.client;
const uptime = process.uptime(); const uptime = process.uptime();
@ -33,21 +32,19 @@ export default {
return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i]; return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i];
}; };
const statsEmbed = new EmbedBuilder() const statsEmbed = new MessageEmbed()
.setColor(interaction.member ? interaction.member.displayHexColor : 'Navy') .setColor(interaction.member ? interaction.member.displayHexColor : 'NAVY')
.setTitle('Bot stats') .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( .addField('Servers', client.guilds.cache.size.toString(), true)
{ name: 'Servers', value: client.guilds.cache.size.toString(), inline: true }, .addField('Channels', client.channels.cache.size.toString(), true)
{ name: 'Channels', value: client.channels.cache.size.toString(), inline: true }, .addField('Users', client.users.cache.size.toString(), true)
{ name: 'Users', value: client.users.cache.size.toString(), inline: true }, .addField('Ram usage', `${bytesToSize(process.memoryUsage().heapUsed)}/${bytesToSize(os.totalmem)}`, true)
{ name: 'Ram usage', value: `${bytesToSize(process.memoryUsage().heapUsed)}/${bytesToSize(os.totalmem)}`, inline: true }, .addField('CPU', `${os.cpus()[0].model} (${os.cpus().length} core)`, true)
{ name: 'CPU', value: `${os.cpus()[0].model} (${os.cpus().length} core) (${os.arch()})`, inline: true }, .addField('OS', `${os.platform()} ${os.release()}`, true)
{ name: 'OS', value: `${os.platform()} ${os.release()}`, inline: true }, .addField('Nodejs version', process.version, true)
{ name: 'Nodejs version', value: process.version, inline: true }, .addField('Discord.js version', version, true)
{ name: 'Discord.js version', value: version, inline: true }, .addField('Uptime', dateString, true)
{ name: 'Uptime', value: dateString, inline: true },
)
.setTimestamp(); .setTimestamp();
return interaction.reply({ embeds: [statsEmbed] }); return interaction.reply({ embeds: [statsEmbed] });

View file

@ -1,69 +0,0 @@
import { SlashCommandBuilder, EmbedBuilder } from 'discord.js';
export default {
data: new SlashCommandBuilder()
.setName('userinfo')
.setDescription('Show info about a user')
.addMentionableOption(option =>
option.setName('user')
.setDescription('Which user you want to see the info of?')
.setRequired(false)),
category: 'utility',
alias: ['user'],
async execute(interaction, args, client) {
await interaction.guild.members.fetch();
let member = interaction.member;
let user = interaction.user;
if (args.user) {
user = client.users.resolve(args.user);
member = interaction.guild.members.resolve(args.user);
}
const Embed = new EmbedBuilder()
.setColor(member ? member.displayHexColor : 'Navy')
.setAuthor({ name: `${user.username} (${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 },
{ name: 'Date when account created', value: user.createdAt.toString(), inline: true },
)
.setTimestamp();
Embed.addFields({ name: '', value: '' });
// Show user status
/* Missing presence intent.
if (user.presence.activities[0]) {
Embed.addField('Presence', user.presence.activities[0], true);
if (user.presence.activities[0].details) Embed.addField('', user.presence.activities[0].details, true);
if (user.presence.activities[0].state) Embed.addField('', user.presence.activities[0].state, true);
}
*/
// Is the user a bot?
if (user.bot) Embed.addFields({ name: 'Is a bot?', value: '✅', inline: true });
// Show on which platform they are using discord from if its not a bot
/* Missing presence intent.
if (user.presence.clientStatus && !user.bot) {
Embed.addFields({ name: '', value: '' });
if (user.presence.clientStatus.mobile) Embed.addFields({ name: 'Using discord on', value: '📱 ' + user.presence.clientStatus.mobile, inline: true });
if (user.presence.clientStatus.desktop) Embed.addFields({ name: 'Using discord on', value: '💻 ' + user.presence.clientStatus.desktop, inline: true });
if (user.presence.clientStatus.web) Embed.addFields({ name: 'Using discord on', value: '☁️ ' + user.presence.clientStatus.web, inline: true });
}
*/
if (member) {
// Show since when this user have been boosting the current guild
if (member.premiumSince) Embed.addFields({ name: 'Boosting this guild since', value: member.premiumSince.toString(), inline: true });
// Show guild nickname
if (member.nickname) Embed.addFields({ name: 'Nickname', value: member.nickname, inline: true });
// Show member roles
if (member.roles) {
Embed.addFields({ name: 'Roles', value: `${[...member.roles.cache.values()].join(', ')}` });
Embed.addFields({ name: 'Permissions', value: `\`${member.permissions.toArray().join(', ')}\`` });
}
}
return interaction.reply({ embeds: [Embed] });
},
};

View file

@ -1,11 +1,10 @@
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from '@discordjs/builders';
import utils from '../../utils/videos.js'; import utils from '../../utils/videos.js';
import fs from 'node:fs'; import fs from 'node:fs';
import os from 'node:os'; import os from 'node:os';
import path from 'node:path'; import path from 'node:path';
import { execFile } from 'node:child_process'; import { exec } from 'node:child_process';
const { NODE_ENV } = process.env;
const ytdlpFormat = 'bestvideo[height<=?480]/best';
export default { export default {
data: new SlashCommandBuilder() data: new SlashCommandBuilder()
@ -14,101 +13,43 @@ export default {
.addStringOption(option => .addStringOption(option =>
option.setName('url') option.setName('url')
.setDescription('URL of the video you want to convert') .setDescription('URL of the video you want to convert')
.setRequired(true)) .setRequired(true)),
.addIntegerOption(option => async execute(interaction) {
option.setName('quality')
.setDescription('Quality of the gif conversion. Default 70. Number between 1 and 100')
.setRequired(false))
.addIntegerOption(option =>
option.setName('fps')
.setDescription('Change the speed at which the gif play at. Number between 1 and 100.')
.setRequired(false))
.addBooleanOption(option =>
option.setName('autocrop')
.setDescription('Autocrop borders on gif.')
.setRequired(false))
.addBooleanOption(option =>
option.setName('noloop')
.setDescription('Stop the gif from looping')
.setRequired(false)),
category: 'utility',
alias: ['v2g', 'togif'],
integration_types: [0, 1],
async execute(interaction, args) {
await interaction.deferReply({ ephemeral: false }); await interaction.deferReply({ ephemeral: false });
const maxFileSize = await utils.getMaxFileSize(interaction.guild); const url = interaction.options.getString('url');
const url = args.url;
let quality = args.quality;
if (quality) {
if (quality <= 0) {
quality = 1;
}
else if (quality > 100) {
quality = 100;
}
}
if (args.fps) {
if (args.fps <= 0) {
args.fps = 1;
}
else if (args.fps > 100) {
args.fps = 100;
}
}
if (!await utils.stringIsAValidurl(url)) { if (!await utils.stringIsAValidurl(url)) {
console.error(`Not a url!!! ${url}`); console.error(`Not a url!!! ${url}`);
return interaction.editReply({ content: '❌ This does not look like a valid url!', ephemeral: true }); return interaction.editReply({ content: '❌ This does not look like a valid url!', ephemeral: true });
} }
const aproxFileSize = await utils.getVideoSize(url, ytdlpFormat); utils.downloadVideo(url, interaction.id)
console.log(aproxFileSize);
if (aproxFileSize > 4) {
return interaction.editReply('The file you are trying to convert is too big! Limit is 4 MB');
};
utils.downloadVideo(url, interaction.id, ytdlpFormat)
.then(async () => { .then(async () => {
const file = fs.readdirSync(os.tmpdir()).filter(fn => fn.startsWith(interaction.id)); const file = fs.readdirSync(os.tmpdir()).filter(fn => fn.startsWith(interaction.id));
let output = `${os.tmpdir()}/${file}`; const output = `${os.tmpdir()}/${file}`;
if (args.autocrop) {
const oldOutput = output;
output = `${os.tmpdir()}/autocrop${file}`;
await utils.autoCrop(oldOutput, output);
}
// Get the fps of the original video
if (!args.fps) {
args.fps = getVideoFramerate(output);
}
const gifskiOutput = output.replace(path.extname(output), '.gif'); const gifskiOutput = output.replace(path.extname(output), '.gif');
const gifsicleOutput = output.replace(path.extname(output), 'gifsicle.gif'); const gifsicleOutput = output.replace(path.extname(output), 'gifsicle.gif');
// Extract every frame for gifski // 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 // Make it look better
await gifski(gifskiOutput, `${os.tmpdir()}/frame${interaction.id}*`, quality, await args.fps); await gifski(gifskiOutput, `${os.tmpdir()}/frame${interaction.id}*`);
// Optimize it // Optimize it
await gifsicle(gifskiOutput, gifsicleOutput, args.noloop); await gifsicle(gifskiOutput, gifsicleOutput);
const fileStat = fs.statSync(gifsicleOutput); const fileStat = fs.statSync(gifsicleOutput);
const fileSize = fileStat.size / 1000000.0; const fileSize = fileStat.size / 1000000.0;
if (fileSize > 25) { if (fileSize > 100) {
await interaction.deleteReply(); await interaction.deleteReply();
await interaction.followUp('❌ Uh oh! The video once converted is too big!', { ephemeral: true }); 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) const fileURL = await utils.upload(gifsicleOutput)
.catch(err => { .catch(err => {
console.error(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 { else {
await interaction.editReply({ files: [gifsicleOutput], ephemeral: false }); await interaction.editReply({ files: [gifsicleOutput], ephemeral: false });
@ -117,50 +58,30 @@ export default {
}, },
}; };
async function gifski(output, input, quality, fps) { async function gifski(output, input) {
return await new Promise((resolve, reject) => { return await new Promise((resolve, reject) => {
// Shell: true should be fine as no user input is being passed exec(`gifski --quality 70 -o ${output} ${input}`, (err, stdout, stderr) => {
execFile('gifski', ['--quality', quality ? quality : 70, '--fps', fps ? fps : 20, '-o', output, input], { shell: true }, (err, stdout, stderr) => {
if (err) { if (err) {
reject(stderr); reject(stderr);
} }
if (stderr) { if (stderr) {
console.error(stderr); console.error(stderr);
} }
console.log(NODE_ENV === 'development' ? stdout : null); resolve(stdout);
resolve();
}); });
}); });
} }
async function gifsicle(input, output, loop = false) { async function gifsicle(input, output) {
return await new Promise((resolve, reject) => { return await new Promise((resolve, reject) => {
// Shell: true should be fine as no user input is being passed exec(`gifsicle --colors 256 -i ${input} -o ${output}`, (err, stdout, stderr) => {
execFile('gifsicle', ['--colors', '256', loop ? '--no-loopcount' : '', '-i', input, '-o', output], { shell: true }, (err, stdout, stderr) => {
if (err) { if (err) {
reject(stderr); reject(stderr);
} }
if (stderr) { if (stderr) {
console.error(stderr); console.error(stderr);
} }
console.log(NODE_ENV === 'development' ? stdout : null); resolve(stdout);
resolve();
});
});
}
async function getVideoFramerate(input) {
return await new Promise((resolve, reject) => {
execFile('ffprobe', ['-v', 'error', '-of', 'csv=p=0', '-select_streams', 'v:0', '-show_entries', 'stream=r_frame_rate', input], (err, stdout, stderr) => {
if (err) {
reject(stderr);
}
if (stderr) {
console.error(stderr);
}
const tempfps = stdout.trim().split('/');
const fps = tempfps[0] / tempfps[1];
return resolve(fps);
}); });
}); });
} }

View file

@ -1,43 +0,0 @@
import { SlashCommandBuilder } from 'discord.js';
import { rand } from '../../utils/rand.js';
import { execFile } from 'node:child_process';
export default {
data: new SlashCommandBuilder()
.setName('dectalk')
.setDescription('Send a .wav sound file of what you wrote with dectalk')
.addStringOption(option =>
option.setName('message')
.setDescription('Write something so I can talk it back with dectalk.')
.setRequired(true)),
category: 'voice',
async execute(interaction, args) {
args.message = rand(args.message, interaction);
const output = `${interaction.id}_dectalk.wav`;
const message = '[:phoneme on]' + args.message;
await interaction.deferReply({ ephemeral: false });
if (process.platform === 'win32') {
// Untested, most likely do not work.
execFile('say.exe', ['-w', output, `${message}`], { cwd: './bin/dectalk/' }, (error, stdout, stderr) => {
sendMessage(output, error, stdout, stderr);
});
}
else if (process.platform === 'linux' || process.platform === 'darwin') {
execFile('wine', ['say.exe', '-w', output, `${message}`], { cwd: './bin/dectalk/' }, (error, stdout, stderr) => {
sendMessage(`./bin/dectalk/${output}`, error, stdout, stderr);
});
}
async function sendMessage(file, error, stdout, stderr) {
if (error) {
console.error(stderr);
console.error(error);
return interaction.editReply('Oh no! an error has occurred!');
}
return interaction.editReply({ files: [file] });
}
},
};

View file

@ -1,14 +0,0 @@
{
"development": {
"dialect": "sqlite",
"storage": "./database.sqlite3"
},
"test": {
"dialect": "sqlite",
"storage": ":memory"
},
"production": {
"dialect": "sqlite",
"storage": "./database.sqlite3"
}
}

View file

@ -1,101 +0,0 @@
import globals from "globals";
import babelParser from "@babel/eslint-parser";
import path from "node:path";
import { fileURLToPath } from "node:url";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all
});
export default [...compat.extends("eslint:recommended"), {
ignores: ["models/*", "migrations/*", "eslint.config.mjs"],
languageOptions: {
globals: {
...globals.node,
},
parser: babelParser,
ecmaVersion: 2022,
sourceType: "module",
parserOptions: {
requireConfigFile: false,
babelOptions: {
plugins: ["@babel/plugin-syntax-import-assertions"],
},
},
},
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",
},
}];

View file

@ -1,26 +1,17 @@
import db from '../../models/index.js'; import db from '../../models/index.js';
const guildBlacklist = db.guildBlacklist; const guildBlacklist = db.guildBlacklist;
import { EmbedBuilder } from 'discord.js'; import { MessageEmbed } from 'discord.js';
import dotenv from 'dotenv';
dotenv.config();
const { statusChannel, NODE_ENV } = process.env; const { statusChannel, NODE_ENV } = process.env;
export default { export default {
name: 'guildCreate', name: 'guildCreate',
once: true,
async execute(guild, client) { async execute(guild, client) {
const guildOwner = await client.users.fetch(guild.ownerId); const guildOwner = await client.users.fetch(guild.ownerId);
const isOptOut = await db.optout.findOne({ where: { userID: guildOwner.id } });
if (isOptOut) {
console.log(`A guild\n${guild.memberCount} users`);
if (statusChannel && NODE_ENV !== 'development') {
const channel = client.channels.resolve(statusChannel);
channel.send({ content: `An anonymous guild just added me.\nI'm now in ${client.guilds.cache.size} servers!` });
}
return;
}
console.log(`${guild.name}\n${guild.memberCount} users\nOwner: ${guildOwner.username}\nOwner ID: ${guild.ownerId}`); console.log(`${guild.name}\n${guild.memberCount} users\nOwner: ${guildOwner.username}\nOwner ID: ${guild.ownerId}`);
const blacklist = await guildBlacklist.findOne({ where: { guildID:guild.id } }); const blacklist = await guildBlacklist.findOne({ where: { guildID:guild.id } });
@ -34,18 +25,16 @@ export default {
const channel = client.channels.resolve(statusChannel); const channel = client.channels.resolve(statusChannel);
const botCount = guild.members.cache.filter(member => member.user.bot).size; const botCount = guild.members.cache.filter(member => member.user.bot).size;
console.log(guild.memberCount); console.log(guild.memberCount);
const addEmbed = new EmbedBuilder() const addEmbed = new MessageEmbed()
.setColor('#52e80d') .setColor('#52e80d')
.setTitle('New boiz in town') .setTitle('New boiz in town')
.setURL('https://www.youtube.com/watch?v=6n3pFFPSlW4') .setURL('https://www.youtube.com/watch?v=6n3pFFPSlW4')
.setThumbnail(guild.iconURL()) .setThumbnail(guild.iconURL())
.addFields( .addField('Guild', `${guild.name} (${guild.id})`)
{ name: 'Guild', value: `${guild.name} (${guild.id})` }, .addField('Total number of members', guild.memberCount.toString(), true)
{ name: 'Total number of members', value: guild.memberCount.toString(), inline: true }, .addField('Number of users', (guild.memberCount - botCount).toString(), true)
{ name: 'Number of users', value: (guild.memberCount - botCount).toString(), inline: true }, .addField('Number of bots', botCount.toString(), true)
{ name: 'Number of bots', value: botCount.toString(), inline: true }, .addField('Owner', `${guildOwner.username} (${guild.ownerId})`, true)
{ name: 'Owner', value: `${guildOwner.username} (${guild.ownerId})`, inline: true },
)
.setFooter({ text: `I'm now in ${client.guilds.cache.size} servers!` }) .setFooter({ text: `I'm now in ${client.guilds.cache.size} servers!` })
.setTimestamp(); .setTimestamp();

View file

@ -1,27 +1,17 @@
import db from '../../models/index.js'; import db from '../../models/index.js';
const guildBlacklist = db.guildBlacklist; const guildBlacklist = db.guildBlacklist;
import { EmbedBuilder } from 'discord.js'; import { MessageEmbed } from 'discord.js';
import dotenv from 'dotenv';
dotenv.config();
const { statusChannel, NODE_ENV } = process.env; const { statusChannel, NODE_ENV } = process.env;
export default { export default {
name: 'guildDelete', name: 'guildDelete',
once: true,
async execute(guild, client) { async execute(guild, client) {
if (!guild.available) return;
const guildOwner = await client.users.fetch(guild.ownerId); const guildOwner = await client.users.fetch(guild.ownerId);
const isOptOut = await db.optout.findOne({ where: { userID: guildOwner.id } });
if (isOptOut) {
console.log(`***BOT KICKED***A guild\n${guild.memberCount} users\n***BOT KICKED***`);
if (statusChannel && NODE_ENV !== 'development') {
const channel = client.channels.resolve(statusChannel);
channel.send({ content: `An anonymous guild just removed me.\nI'm now in ${client.guilds.cache.size} servers!` });
}
return;
}
console.log(`***BOT KICKED***\n${guild.name}\n${guild.memberCount} users\nOwner: ${guildOwner.username}\nOwner ID: ${guild.ownerId}\n***BOT KICKED***`); console.log(`***BOT KICKED***\n${guild.name}\n${guild.memberCount} users\nOwner: ${guildOwner.username}\nOwner ID: ${guild.ownerId}\n***BOT KICKED***`);
const blacklist = await guildBlacklist.findOne({ where: { guildID:guild.id } }); const blacklist = await guildBlacklist.findOne({ where: { guildID:guild.id } });
@ -31,18 +21,16 @@ export default {
const channel = client.channels.resolve(statusChannel); const channel = client.channels.resolve(statusChannel);
const botCount = guild.members.cache.filter(member => member.user.bot).size; const botCount = guild.members.cache.filter(member => member.user.bot).size;
console.log(guild.memberCount); console.log(guild.memberCount);
const kickEmbed = new EmbedBuilder() const kickEmbed = new MessageEmbed()
.setColor('#FF0000') .setColor('#FF0000')
.setTitle('Some mofo just removed me from there guild :(') .setTitle('Some mofo just removed me from there guild :(')
.setURL('https://www.youtube.com/watch?v=6n3pFFPSlW4') .setURL('https://www.youtube.com/watch?v=6n3pFFPSlW4')
.setThumbnail(guild.iconURL()) .setThumbnail(guild.iconURL())
.addFields( .addField('Guild', `${guild.name} (${guild.id})`)
{ name: 'Guild', value: `${guild.name} (${guild.id})` }, .addField('Total number of members', guild.memberCount.toString(), true)
{ name: 'Total number of members', value: guild.memberCount.toString(), inline: true }, .addField('Number of users', (guild.memberCount - botCount).toString(), true)
{ name: 'Number of users', value: (guild.memberCount - botCount).toString(), inline: true }, .addField('Number of bots', botCount.toString(), true)
{ name: 'Number of bots', value: botCount.toString(), inline: true }, .addField('Owner', `${guildOwner.username} (${guild.ownerId})`, true)
{ name: 'Owner', value: `${guildOwner.username} (${guild.ownerId})`, inline: true },
)
.setFooter({ text: `I'm now in ${client.guilds.cache.size} servers!` }) .setFooter({ text: `I'm now in ${client.guilds.cache.size} servers!` })
.setTimestamp(); .setTimestamp();

View file

@ -1,56 +0,0 @@
import db from '../../models/index.js';
import { rand } from '../../utils/rand.js';
export default {
name: 'guildMemberAdd',
async execute(member, client) {
const isOptOut = await db.optout.findOne({ where: { userID: member.user.id } });
if (isOptOut) return;
const join = await db.joinChannel.findOne({ where: { guildID: member.guild.id } });
if (join) {
const channel = client.channels.resolve(join.get('channelID'));
let welcomeMessage = join.get('message');
const invite = new RegExp(/(https?:\/\/)?(www\.)?discord(?:app\.com|\.gg)[/invite/]?(?:(?!.*[Ii10OolL]).[a-zA-Z0-9]{5,6}|[a-zA-Z0-9-]{2,32})/g);
let username = member.user.username;
let user = member.user;
if (username.match(invite)) {
username = username.replace(/(https?:\/\/)?(www\.)?discord(?:app\.com|\.gg)[/invite/]?(?:(?!.*[Ii10OolL]).[a-zA-Z0-9]{5,6}|[a-zA-Z0-9-]{2,32})/g, '[REDACTED]');
user = username;
}
welcomeMessage = welcomeMessage.replace(/\[member\]/g, username);
welcomeMessage = welcomeMessage.replace(/\[memberPing\]/g, user);
welcomeMessage = welcomeMessage.replace(/\[server\]/g, member.guild.name);
// add attachment
let attach;
if (welcomeMessage.includes('[attach:')) {
attach = welcomeMessage.split(/(\[attach:.*?])/);
for (let i = 0, l = attach.length; i < l; i++) {
if (attach[i].includes('[attach:')) {
attach = attach[i].replace('[attach:', '').slice(0, -1);
i = attach.length;
}
}
welcomeMessage = welcomeMessage.replace(/(\[attach:.*?])/, '');
}
welcomeMessage = rand(welcomeMessage);
if (attach) {
return channel.send({ content: welcomeMessage, files: [attach] });
}
else {
return channel.send({ content: welcomeMessage });
}
}
},
};

View file

@ -1,56 +0,0 @@
import db from '../../models/index.js';
import { rand } from '../../utils/rand.js';
export default {
name: 'guildMemberRemove',
async execute(member, client) {
const isOptOut = await db.optout.findOne({ where: { userID: member.user.id } });
if (isOptOut) return;
const leave = await db.leaveChannel.findOne({ where: { guildID: member.guild.id } });
if (leave) {
const channel = client.channels.resolve(leave.get('channelID'));
let welcomeMessage = leave.get('message');
const invite = new RegExp(/(https?:\/\/)?(www\.)?discord(?:app\.com|\.gg)[/invite/]?(?:(?!.*[Ii10OolL]).[a-zA-Z0-9]{5,6}|[a-zA-Z0-9-]{2,32})/g);
let username = member.user.username;
let user = member.user;
if (username.match(invite)) {
username = username.replace(/(https?:\/\/)?(www\.)?discord(?:app\.com|\.gg)[/invite/]?(?:(?!.*[Ii10OolL]).[a-zA-Z0-9]{5,6}|[a-zA-Z0-9-]{2,32})/g, '[REDACTED]');
user = username;
}
welcomeMessage = welcomeMessage.replace(/\[member\]/g, username);
welcomeMessage = welcomeMessage.replace(/\[memberPing\]/g, user);
welcomeMessage = welcomeMessage.replace(/\[server\]/g, member.guild.name);
// add attachment
let attach;
if (welcomeMessage.includes('[attach:')) {
attach = welcomeMessage.split(/(\[attach:.*?])/);
for (let i = 0, l = attach.length; i < l; i++) {
if (attach[i].includes('[attach:')) {
attach = attach[i].replace('[attach:', '').slice(0, -1);
i = attach.length;
}
}
welcomeMessage = welcomeMessage.replace(/(\[attach:.*?])/, '');
}
welcomeMessage = rand(welcomeMessage);
if (attach) {
return channel.send({ content: welcomeMessage, files: [attach] });
}
else {
return channel.send({ content: welcomeMessage });
}
}
},
};

View file

@ -1,28 +1,19 @@
// TODO: Moving that to a dedicated function that works for both messages and interactions import { Permissions } from 'discord.js';
import { PermissionFlagsBits, InteractionType } from 'discord.js';
import db from '../../models/index.js'; import db from '../../models/index.js';
import ratelimiter from '../../utils/ratelimiter.js'; const ratelimit = {};
import dotenv from 'dotenv';
dotenv.config();
const { ownerId } = process.env; const { ownerId } = process.env;
export default { export default {
name: 'interactionCreate', name: 'interactionCreate',
async execute(interaction) { async execute(interaction) {
const client = interaction.client; const client = interaction.client;
if (interaction.type !== InteractionType.ApplicationCommand) return; if (!interaction.isCommand()) return;
const globalBlacklist = await db.Blacklists.findOne({ where: { type:'global', uid:interaction.user.id } }); const globalBlacklist = await db.Blacklists.findOne({ where: { type:'global', uid:interaction.user.id } });
const commandBlacklist = await db.Blacklists.findOne({ where: { type:interaction.commandName, uid:interaction.user.id } }); const commandBlacklist = await db.Blacklists.findOne({ where: { type:interaction.commandName, uid:interaction.user.id } });
if (interaction.guild) {
const serverBlacklist = await db.Blacklists.findOne({ where: { type:'guild', uid:interaction.guild.id } });
if (serverBlacklist) {
interaction.reply({ content: `This guild has been blacklisted for the following reason: \`${serverBlacklist.reason}\``, ephemeral: true });
return interaction.guild.leave();
}
}
if (globalBlacklist) { if (globalBlacklist) {
return interaction.reply({ content: `You are globally blacklisted for the following reason: \`${globalBlacklist.reason}\``, ephemeral: true }); return interaction.reply({ content: `You are globally blacklisted for the following reason: \`${globalBlacklist.reason}\``, ephemeral: true });
} }
@ -30,7 +21,7 @@ export default {
return interaction.reply({ content: `You are blacklisted for the following reason: \`${commandBlacklist.reason}\``, ephemeral: true }); 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 userID = interaction.user.id;
const commandName = interaction.commandName; const commandName = interaction.commandName;
@ -38,91 +29,56 @@ export default {
if (!command) return; if (!command) return;
let isOptOut = await db.optout.findOne({ where: { userID: interaction.user.id } }); console.log(`\x1b[33m${userTag} (${userID})\x1b[0m launched command \x1b[33m${commandName}\x1b[0m`);
if (commandName === 'optout') {
isOptOut = true;
}
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`);
// Owner only check // Owner only check
if (command.ownerOnly && interaction.user.id !== ownerId) { if (command.ownerOnly && interaction.user.id !== ownerId) {
return interaction.reply({ content: '❌ This command is reserved for the owner!', ephemeral: true }); 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 // Check if the bot has the needed permissions
if (command.default_permission) { if (command.clientPermissions) {
const clientMember = await interaction.guild.members.fetch(client.user.id); const clientMember = await interaction.guild.members.fetch(client.user.id);
if (!clientMember.permissions.has(command.clientPermissions)) { if (!clientMember.permissions.has(command.clientPermissions)) {
return interaction.reply({ content: `❌ I am missing one of the following permission(s): \`${new PermissionFlagsBits(command.clientPermissions).toArray()}\``, ephemeral: true }); return interaction.reply({ content: `❌ I am missing one of the following permission(s): \`${new Permissions(command.clientPermissions).toArray()}\``, ephemeral: true });
} }
} }
// Check if the user has the needed permissions // Check if the user has the needed permissions
/* if (command.userPermissions) {
if (command.default_member_permissions) {
if (!interaction.member.permissions.has(command.userPermissions)) { if (!interaction.member.permissions.has(command.userPermissions)) {
return interaction.reply({ content: `❌ You are missing one of the following permission(s): \`${new PermissionFlagsBits(command.userPermissions).toArray()}\``, ephemeral: true }); return interaction.reply({ content: `❌ You are missing one of the following permission(s): \`${new Permissions(command.userPermissions).toArray()}\``, ephemeral: true });
} }
} }
*/
// 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);
if (doRateLimit) {
return interaction.reply({ content: doRateLimit, ephemeral: true });
}
try { try {
interaction.prefix = '/'; const date = new Date();
if (ratelimit[userID]) {
const args = {}; if (ratelimit[userID].cooldown) {
// https://discord-api-types.dev/api/discord-api-types-v10/enum/ApplicationCommandOptionType if (commandName === ratelimit[userID].command && date > ratelimit[userID].cooldown) {
interaction.options.data.forEach(arg => { ratelimit[userID].limit = 0;
let payload = arg.value; ratelimit[userID].cooldown = undefined;
if (arg.type === 9) {
payload = arg.member;
} }
else if (arg.type === 11) {
payload = arg.attachment;
}
args[arg.name] = payload;
});
if (!isOptOut) {
console.log(`[${timestamp.toISOString()}] \x1b[33m⤷\x1b[0m with args ${JSON.stringify(args)}`);
} }
await command.execute(interaction, args, client) if (commandName === ratelimit[userID].command && command.ratelimit === ratelimit[userID].limit) {
.then(async () => { return await interaction.reply({ content: `You are being rate limited. You can try again in ${Math.floor((ratelimit[userID].cooldown - date) / 1000)} seconds.`, ephemeral: true });
const hasPrallelLimit = await ratelimiter.checkParallel(interaction.user, commandName, command);
if (hasPrallelLimit) ratelimiter.removeParallel(commandName); }
}); }
if (command.ratelimit) {
ratelimit[userID] = { command: commandName, limit: ratelimit[userID] ? ratelimit[userID].limit + 1 : 1 };
if (command.ratelimit === ratelimit[userID].limit) {
date.setSeconds(date.getSeconds() + command.cooldown);
ratelimit[userID] = { command: commandName, limit: ratelimit[userID].limit, cooldown: date };
}
}
await command.execute(interaction);
} }
catch (error) { catch (error) {
console.error(error); console.error(error);
const hasPrallelLimit = await ratelimiter.checkParallel(interaction.user, commandName, command); await interaction.followUp({ content: 'There was an error while executing this command!', ephemeral: true });
if (hasPrallelLimit) ratelimiter.removeParallel(commandName);
await interaction.followUp({ content: `There was an error while executing this command!\n\`${error}\``, ephemeral: true });
} }
}, },
}; };

View file

@ -1,471 +0,0 @@
/* TODO:
* Make this shit work.
*/
import { ApplicationCommandOptionType, EmbedBuilder, PermissionFlagsBits } from 'discord.js';
import db from '../../models/index.js';
import { rand } from '../../utils/rand.js';
import ratelimiter from '../../utils/ratelimiter.js';
const { ownerId, prefix } = process.env;
const prefixs = prefix.split(',');
export default {
name: 'messageCreate',
async execute(message, client) {
if (message.partials) {
await message.fetch();
}
if (message.author.bot) return;
/* Autoresponse feature & tag
*
* This section contains autoresponse and tag feature
*
*/
// auto responses
if (message.guild) {
const autoresponseStat = await db.autoresponseStat.findOne({ where: { serverID: message.guild.id, stat: 'enable' } });
if (autoresponseStat) {
// Infinite haha very yes
if (message.content.toLowerCase().startsWith('haha very') && message.content.toLowerCase().endsWith('yes')) {
let yes = message.content.toLowerCase().replace('haha', '');
yes = yes.replace('yes', '');
yes += 'very';
return message.channel.send(`haha${yes} yes`);
}
else if (message.content.toLowerCase() == 'haha yes') {
return message.channel.send('haha very yes');
}
// Reply with images as attachement
const autoresponse = await db.autoresponse.findOne({ where: { trigger: message.content.toLowerCase() } });
if (autoresponse) {
db.autoresponse.findOne({ where: { trigger: message.content.toLowerCase() } });
const trigger = autoresponse.get('trigger');
const type = autoresponse.get('type');
const content = autoresponse.get('response');
if (trigger == message.content.toLowerCase() && type == 'text') {
return message.channel.send(content);
}
else if (trigger == message.content.toLowerCase() && type == 'react') {
return message.react(content);
}
else if (trigger == message.content.toLowerCase() && type == 'image') {
return message.channel.send({ files: [content] });
}
}
}
// User autoresponse
const tag = await db.Tag.findOne({ where: { trigger: message.content.toLowerCase(), serverID: message.guild.id } });
if (tag) {
db.Tag.findOne({ where: { trigger: message.content.toLowerCase(), serverID: message.guild.id } });
let text = tag.get('response');
/*
if (text.includes('[ban]')) {
message.member.ban('Tag ban :^)');
}
else if (text.includes('[kick]')) {
message.member.kick('Tag kick :^)');
}
else
*/
if (text.includes('[delete]')) {
message.delete();
}
text = rand(text, message);
let attach = '';
if (text.includes('[attach:')) {
attach = text.split(/(\[attach:.*?])/);
for (let i = 0, l = attach.length; i < l; i++) {
if (attach[i].includes('[attach:')) {
attach = attach[i].replace('[attach:', '').slice(0, -1);
i = attach.length;
}
}
text = text.replace(/(\[attach:.*?])/, '');
}
// THIS SECTION IS VERY VERY BAD MUST CHANGE
if (text.includes('[embed]')) {
text = text.replace(/\[embed\]/, ' ');
let title = '';
let desc = '';
let image;
let thumbnail;
let footer = '';
let color;
if (text.includes('[embedImage:')) {
image = text.split(/(\[embedImage:.*?])/);
for (let i = 0, l = image.length; i < l; i++) {
if (image[i].includes('[embedImage:')) {
image = image[i].replace('[embedImage:', '').slice(0, -1);
text = text.replace(/(\[embedimage:.*?])/g, '');
i = image.length;
}
}
}
if (text.includes('[embedThumbnail:')) {
thumbnail = text.split(/(\[embedThumbnail:.*?])/);
for (let i = 0, l = thumbnail.length; i < l; i++) {
if (thumbnail[i].includes('[embedThumbnail:')) {
thumbnail = thumbnail[i].replace('[embedThumbnail:', '').slice(0, -1);
text = text.replace(/(\[embedThumbnail:.*?])/g, '');
i = thumbnail.length;
}
}
}
if (text.includes('[embedColor:')) {
color = text.split(/(\[embedColor:.*?])/);
for (let i = 0, l = color.length; i < l; i++) {
if (color[i].includes('[embedColor:')) {
color = color[i].replace('[embedColor:', '').slice(0, -1);
text = text.replace(/(\[embedColor:.*?])/g, '');
i = color.length;
}
}
}
if (text.includes('[embedTitle:')) {
title = text.split(/(\[embedTitle:.*?])/);
for (let i = 0, l = title.length; i < l; i++) {
if (title[i].includes('[embedTitle:')) {
title = title[i].replace('[embedTitle:', '').slice(0, -1);
text = text.replace(/(\[embedTitle:.*?])/g, '');
i = title.length;
}
}
}
if (text.includes('[embedFooter:')) {
footer = text.split(/(\[embedFooter:.*?])/);
for (let i = 0, l = footer.length; i < l; i++) {
if (footer[i].includes('[embedFooter:')) {
footer = footer[i].replace('[embedFooter:', '').slice(0, -1);
text = text.replace(/(\[embedFooter:.*?])/g, '');
i = footer.length;
}
}
}
if (text.includes('[embedDesc:')) {
desc = text.split(/(\[embedDesc:.*?])/);
for (let i = 0, l = desc.length; i < l; i++) {
if (desc[i].includes('[embedDesc:')) {
desc = desc[i].replace('[embedDesc:', '').slice(0, -1);
i = desc.length;
}
}
}
const embed = new EmbedBuilder()
.setColor(color)
.setTitle(title)
.setImage(image)
.setThumbnail(thumbnail)
.setDescription(desc)
.setFooter(footer)
.setTimestamp();
if (attach) {
return message.channel.send(embed, { files: [attach] });
}
else {
return message.channel.send(embed);
}
}
if (attach) {
return message.channel.send(text, { files: [attach] });
}
else {
return message.channel.send(text);
}
}
/* Quotation feature
*
* This section will contain the code for the quotation feature, it will detect link for it and send it as embed
*
*/
const isOptOut = await db.optout.findOne({ where: { userID: message.author.id } });
if (!isOptOut) {
const quotationstat = await db.quotationStat.findOne({ where: { serverID: message.guild.id, stat: 'enable' } });
if (quotationstat && (message.content.includes('discordapp.com/channels/') || message.content.includes('discord.com/channels/'))) {
const url = message.content.split('/');
const guildID = url[4];
const channelID = url[5];
const messageID = url[6].split(' ')[0];
// Verify if the guild, channel and message exist
const guild = client.guilds.resolve(guildID);
if (!guild) return;
const channel = client.channels.resolve(channelID);
if (!channel) return;
const quote = await channel.messages.fetch(messageID)
.catch(() => {
return;
});
if (!quote) return;
const Embed = new EmbedBuilder()
.setAuthor({ name: quote.author.username, iconURL: quote.author.displayAvatarURL() })
.setColor(message.member ? message.member.displayHexColor : 'Navy')
.addFields(
{ name: 'Jump to', value: `[message](https://discordapp.com/channels/${message.guild.id}/${channelID}/${messageID})`, inline: true },
{ name: 'In channel', value: quote.channel.name.toString(), inline: true },
{ name: 'Quoted by', value: message.author.toString(), inline: true },
)
.setDescription(quote.content)
.setTimestamp(quote.createdTimestamp);
if (quote.member) Embed.setAuthor({ name: `${quote.author.username}#${quote.author.discriminator}`, iconURL: quote.author.displayAvatarURL() });
if (quote.author.bot) Embed.setAuthor({ name: `${quote.author.username}#${quote.author.discriminator} (BOT)`, iconURL: quote.author.displayAvatarURL() });
if (guild.id != message.guild.id) Embed.addFields({ name: 'In guild', value: guild.name, inline: true });
const Attachment = Array.from(message.attachments.values());
if (Attachment[0]) Embed.setImage(Attachment[0].url);
return message.channel.send({ embeds: [Embed] });
}
}
}
// Command handling from message
// TODO: Moving that to a dedicated function that works for both messages and interactions
let hasPrefix = false;
prefixs.forEach(p => {
if (message.content.toLowerCase().startsWith(p)) {
hasPrefix = true;
}
});
if (!hasPrefix) return;
const messageArray = message.content.match(/"[^"]*"|\S+/g).map(m => m.slice(0, 1) === '"' ? m.slice(1, -1) : m);
let commandName = messageArray[1].toLowerCase();
const messageArgs = messageArray.splice(2, messageArray.length);
// Search for alias
client.commands.find(c => {
if (c.alias) {
if (c.alias.includes(commandName)) {
commandName = c.data.name;
}
}
});
const command = client.commands.get(commandName);
if (!command) return;
const globalBlacklist = await db.Blacklists.findOne({ where: { type:'global', uid:message.author.id } });
const commandBlacklist = await db.Blacklists.findOne({ where: { type:commandName, uid:message.author.id } });
if (message.guild) {
const serverBlacklist = await db.Blacklists.findOne({ where: { type:'guild', uid:message.guild.id } });
if (serverBlacklist) {
message.reply({ content: `This guild has been blacklisted for the following reason: \`${serverBlacklist.reason}\``, ephemeral: true });
return message.guild.leave();
}
}
if (globalBlacklist) {
return message.reply({ content: `You are globally blacklisted for the following reason: \`${globalBlacklist.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 userID = message.author.id;
let isOptOut = await db.optout.findOne({ where: { userID: message.author.id } });
if (commandName === 'optout') {
isOptOut = true;
}
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`);
// 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);
if (!clientMember.permissions.has(command.clientPermissions)) {
return message.reply({ content: `❌ I am missing one of the following permission(s): \`${new PermissionFlagsBits(command.clientPermissions).toArray()}\``, ephemeral: true });
}
}
// Check if the user has the needed permissions
if (command.default_member_permissions) {
if (!message.member.permissions.has(command.default_member_permissions)) {
return message.reply({ content: `❌ You are missing one of the following permission(s): \`${new PermissionFlagsBits(command.userPermissions).toArray()}\``, ephemeral: true });
}
}
// 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);
if (doRateLimit) {
return message.reply({ content: doRateLimit, ephemeral: true });
}
try {
message.user = message.author;
message.isMessage = true;
message.prefix = `${messageArray[0]} `;
let waitingmsg;
const toDelete = [];
message.deferReply = async function() {
waitingmsg = await message.reply('The bot is thinking...');
};
message.followUp = async function(payload) {
if (payload.ephemeral) {
toDelete.push(await message.channel.send(payload));
}
else {
await message.channel.send(payload);
}
};
message.editReply = async function(payload) {
if (waitingmsg) {
await waitingmsg.delete();
}
await message.channel.send(payload);
};
message.deleteReply = async function() {
if (waitingmsg) {
await waitingmsg.delete()
.catch(() => { return; });
}
};
message.cleanUp = async function() {
toDelete.forEach(async msg => {
msg.delete();
});
};
const args = {};
let argsToDelete = 0;
command.data.options.forEach(obj => {
if (obj.type === ApplicationCommandOptionType.Attachment) {
args[obj.name] = message.attachments.first();
delete command.data.options[command.data.options.indexOf(obj)];
argsToDelete++;
}
});
const argsLength = command.data.options.length - argsToDelete;
const missingRequired = [];
for (let i = 0, j = 0; i < argsLength; i++, j++) {
const arg = command.data.options[j];
if (arg.required && !messageArgs[i]) {
missingRequired.push({ name: arg.name, description: arg.description });
}
if (!messageArgs[i]) continue;
if (arg.type === ApplicationCommandOptionType.Attachment) continue;
let payloadName = arg.name;
let payload = messageArgs[i];
if (i >= argsLength - 1) {
payload = messageArgs.slice(i).join(' ');
}
if (arg.type === ApplicationCommandOptionType.Boolean && !messageArgs[i].startsWith('--')) {
continue;
}
else if (messageArgs[i].startsWith('--')) {
payloadName = payload.substring(2);
payload = true;
j--;
}
if (arg.type === ApplicationCommandOptionType.Mentionable || arg.type === ApplicationCommandOptionType.User) {
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()));
}
args[payloadName] = payload;
}
if (!isOptOut && argsLength > 0) {
console.log(`[${timestamp.toISOString()}] \x1b[33m⤷\x1b[0m with args ${JSON.stringify(args)}`);
}
if (missingRequired.length > 0) {
let missingMsg = '';
missingRequired.forEach(arg => {
missingMsg += `${arg.name} | ${arg.description}\n`;
});
return message.reply(`You are missing a required argument!\n\`${missingMsg}\``);
}
await command.execute(message, args, client)
.then(async () => {
const hasPrallelLimit = await ratelimiter.checkParallel(message.author, commandName, command);
if (hasPrallelLimit) ratelimiter.removeParallel(commandName);
});
}
catch (error) {
console.error(error);
const hasPrallelLimit = await ratelimiter.checkParallel(message.author, commandName, command);
if (hasPrallelLimit) ratelimiter.removeParallel(commandName);
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}\`` });
});
}
},
};

View file

@ -1,171 +0,0 @@
import { EmbedBuilder } from 'discord.js';
import fs from 'node:fs';
import db from '../../models/index.js';
export default {
name: 'messageReactionAdd',
async execute(reaction, users, c) {
if (reaction.partial) {
await reaction.fetch()
.catch(err => {
return console.error(err);
});
}
/* I don't really know why this is causing issues.
if (reaction.message.partial) {
await reaction.message.fetch()
.catch(err => {
return console.error(err);
});
}
*/
const isOptOut = await db.optout.findOne({ where: { userID: reaction.message.author.id } });
if (isOptOut) return;
let starboardChannel, shameboardChannel;
let reactionCount = reaction.count;
// If one of the reaction is the author of the message remove 1 to the reaction count
reaction.users.cache.forEach(user => {
if (reaction.message.author == user) reactionCount--;
});
// Starboard
if (fs.existsSync(`./json/board/star${reaction.message.guild.id}.json`)) {
starboardChannel = JSON.parse(fs.readFileSync(`./json/board/star${reaction.message.guild.id}.json`));
let staremote = starboardChannel.emote;
const starcount = starboardChannel.count;
// Get name of the custom emoji
if (reaction.message.guild.emojis.resolve(staremote.replace(/\D/g, ''))) {
staremote = reaction.message.guild.emojis.resolve(staremote.replace(/\D/g, ''));
}
if (reaction.emoji == staremote || reaction.emoji.name == staremote) {
if (global.boards[reaction.message.id] && reactionCount > starcount) {
return editEmbed('starboard', staremote, global.boards[reaction.message.id]);
}
else if (reactionCount == starcount) {
return sendEmbed('starboard', staremote);
}
}
}
// Shameboard
if (fs.existsSync(`./json/board/shame${reaction.message.guild.id}.json`)) {
shameboardChannel = JSON.parse(fs.readFileSync(`./json/board/shame${reaction.message.guild.id}.json`));
let shameemote = shameboardChannel.emote;
const shamecount = shameboardChannel.count;
// Get name of the custom emoji
if (reaction.message.guild.emojis.resolve(shameemote.replace(/\D/g, ''))) {
shameemote = reaction.message.guild.emojis.resolve(shameemote.replace(/\D/g, ''));
}
if (reaction.emoji == shameemote || reaction.emoji.name == shameemote) {
if (global.boards[reaction.message.id] && reactionCount > shamecount) {
return editEmbed('shameboard', shameemote, global.boards[reaction.message.id]);
}
else if (reactionCount == shamecount) {
return sendEmbed('shameboard', shameemote);
}
}
}
async function editEmbed(name, emote, boardID) {
let channel;
if (name == 'starboard') {
channel = c.channels.resolve(starboardChannel.starboard);
}
else {
channel = c.channels.resolve(shameboardChannel.shameboard);
}
const message = await channel.messages.resolve(boardID);
// If the message doesn't have embeds assume it got deleted so don't do anything
if (!message) return;
// If the original embed description is empty make this embed null ( and not empty )
let description = message.embeds[0].description;
if (!message.embeds[0].description || message.embeds[0].description == undefined) {
description = null;
}
const Embed = new EmbedBuilder()
.setColor(reaction.message.member ? reaction.message.member.displayHexColor : 'Navy')
.setAuthor({ name: reaction.message.author.username, iconURL: reaction.message.author.displayAvatarURL() })
.addFields(
{ name: 'Jump to', value: `[message](https://discordapp.com/channels/${reaction.message.guild.id}/${reaction.message.channel.id}/${reaction.message.id})`, inline: true },
{ name: 'Channel', value: reaction.message.channel.toString(), inline: true },
)
.setDescription(description)
.setFooter({ text: `${emote} ${reactionCount}` })
.setTimestamp();
if (reaction.message.guild.emojis.resolve(emote)) Embed.setFooter(reactionCount, reaction.message.guild.emojis.resolve(emote).url);
message.edit({ embeds: [Embed] });
}
async function sendEmbed(name, emote) {
let messageAttachments = reaction.message.attachments.map(u => u.url)[0];
// Should change this so it automatically pick the channel ( I'm lazy right now )
let channel;
if (name == 'starboard') {
channel = c.channels.resolve(starboardChannel.starboard);
}
else {
channel = c.channels.resolve(shameboardChannel.shameboard);
}
const Embed = new EmbedBuilder()
.setColor(reaction.message.member ? reaction.message.member.displayHexColor : 'Navy')
.setAuthor({ name: reaction.message.author.username, iconURL: reaction.message.author.displayAvatarURL() })
.addFields(
{ name: 'Jump to', value: `[message](https://discordapp.com/channels/${reaction.message.guild.id}/${reaction.message.channel.id}/${reaction.message.id})`, inline: true },
{ name: 'Channel', value: reaction.message.channel.toString(), inline: true },
)
.setFooter({ text: `${emote} ${reactionCount}` })
.setTimestamp();
if (reaction.message.guild.emojis.resolve(emote)) Embed.setFooter({ text: `${reactionCount}`, iconURL: reaction.message.guild.emojis.resolve(emote).url });
let description = null;
if (reaction.message.embeds[0]) {
if (reaction.message.embeds[0].type == 'image') {
messageAttachments = reaction.message.embeds[0].url;
}
if (reaction.message.embeds[0].description) {
description = reaction.message.embeds[0].description;
}
else if (reaction.message.content) {
description = reaction.message.content;
}
}
else if (reaction.message.content) {
description = reaction.message.content;
}
// if message come from nsfw channel and the star/shameboard channel isn't nsfw put it in spoiler
if (reaction.message.channel.nsfw && !channel.nsfw) {
Embed.setDescription(`||${description}||`);
if (messageAttachments != '') {
const message = await channel.send({ content: `||${messageAttachments}||`, embeds: [Embed] });
global.boards[reaction.message.id] = message.id;
}
else {
const message = await channel.send({ embeds: [Embed] });
global.boards[reaction.message.id] = message.id;
}
}
else {
Embed.setDescription(description);
const message = await channel.send({ files: [messageAttachments], embeds: [Embed] })
.catch(async () => channel.send({ content: messageAttachments, embeds: [Embed] }));
global.boards[reaction.message.id] = message.id;
}
}
},
};

View file

@ -1,119 +0,0 @@
import { EmbedBuilder } from 'discord.js';
import fs from 'node:fs';
import db from '../../models/index.js';
export default {
name: 'messageReactionRemove',
async execute(reaction, users, c) {
if (reaction.partial) {
await reaction.fetch()
.catch(err => {
return console.error(err);
});
}
/* I don't really know why this is causing issues.
if (reaction.message.partial) {
await reaction.message.fetch()
.catch(err => {
return console.error(err);
});
}
*/
const isOptOut = await db.optout.findOne({ where: { userID: reaction.message.author.id } });
if (isOptOut) return;
let starboardChannel, shameboardChannel;
let reactionCount = reaction.count;
// If one of the reaction is the author of the message remove 1 to the reaction count
reaction.users.cache.forEach(user => {
if (reaction.message.author == user) reactionCount--;
});
// Starboard
if (fs.existsSync(`./json/board/star${reaction.message.guild.id}.json`)) {
starboardChannel = JSON.parse(fs.readFileSync(`./json/board/star${reaction.message.guild.id}.json`));
let staremote = starboardChannel.emote;
const starcount = starboardChannel.count;
// Get name of the custom emoji
if (reaction.message.guild.emojis.resolve(staremote.replace(/\D/g, ''))) {
staremote = reaction.message.guild.emojis.resolve(staremote.replace(/\D/g, ''));
}
if (global.boards[reaction.message.id] && (reaction.emoji == staremote || reaction.emoji.name == staremote) && reactionCount < starcount) {
const channel = c.channels.resolve(starboardChannel.starboard);
const message = await channel.messages.resolve(global.boards[reaction.message.id]);
delete global.boards[reaction.message.id];
// If it didn't find any message don't do anything
if (!message) return;
message.delete();
}
else if ((reaction.emoji == staremote || reaction.emoji.name == staremote) && reactionCount >= starcount) {
return editEmbed('starboard', staremote, global.boards[reaction.message.id]);
}
}
// Shameboard
if (fs.existsSync(`./json/board/shame${reaction.message.guild.id}.json`)) {
shameboardChannel = JSON.parse(fs.readFileSync(`./json/board/shame${reaction.message.guild.id}.json`));
let shameemote = shameboardChannel.emote;
const shamecount = shameboardChannel.count;
// Get name of the custom emoji
if (reaction.message.guild.emojis.resolve(shameemote.replace(/\D/g, ''))) {
shameemote = reaction.message.guild.emojis.resolve(shameemote.replace(/\D/g, ''));
}
if (global.boards[reaction.message.id] && (reaction.emoji == shameemote || reaction.emoji.name == shameemote) && reactionCount < shamecount) {
const channel = c.channels.resolve(starboardChannel.starboard);
const message = await channel.messages.resolve(global.boards[reaction.message.id]);
delete global.boards[reaction.message.id];
// If it didn't find any message don't do anything
if (!message) return;
message.delete();
}
else if ((reaction.emoji == shameemote || reaction.emoji.name == shameemote) && reactionCount >= shamecount) {
return editEmbed('shameboard', shameemote, global.boards[reaction.message.id]);
}
}
async function editEmbed(name, emote, boardID) {
let channel;
if (name == 'starboard') {
channel = c.channels.resolve(starboardChannel.starboard);
}
else {
channel = c.channels.resolve(shameboardChannel.shameboard);
}
const message = await channel.messages.resolve(boardID);
// If the message doesn't have embeds assume it got deleted so don't do anything
if (!message) return;
// If the original embed description is empty make this embed null ( and not empty )
let description = message.embeds[0].description;
if (!message.embeds[0].description || message.embeds[0].description == undefined) {
description = null;
}
const Embed = new EmbedBuilder()
.setColor(reaction.message.member ? reaction.message.member.displayHexColor : 'Navy')
.setAuthor({ name: reaction.message.author.username, iconURL: reaction.message.author.displayAvatarURL() })
.addFields(
{ name: 'Jump to', value: `[message](https://discordapp.com/channels/${reaction.message.guild.id}/${reaction.message.channel.id}/${reaction.message.id})`, inline: true },
{ name: 'Channel', value: reaction.message.channel.toString(), inline: true },
)
.setDescription(description)
.setFooter({ text: `${emote} ${reactionCount}` })
.setTimestamp();
if (reaction.message.guild.emojis.resolve(emote)) Embed.setFooter({ text: `${reactionCount}`, iconURL: reaction.message.guild.emojis.resolve(emote).url });
message.edit({ embeds: [Embed] });
}
},
};

View file

@ -1,29 +1,14 @@
import { execFile } from 'node:child_process'; import { exec } from 'node:child_process';
import dotenv from 'dotenv';
dotenv.config();
const { statusChannel, NODE_ENV } = process.env; const { statusChannel, NODE_ENV } = process.env;
import { version } from 'discord.js';
export default { export default {
name: 'ready', name: 'ready',
once: true, once: true,
async execute(client) { async execute(client) {
// Init global variables.
global.boards = {};
const commandSize = client.commands.size;
const clientTag = client.user.username;
const guildSize = client.guilds.cache.size;
const channelSize = client.channels.cache.size;
const clientID = client.user.id;
console.log('===========[ READY ]===========');
console.log(`\x1b[32mLogged in as \x1b[34m${clientTag}\x1b[0m! (\x1b[33m${clientID}\x1b[0m)`);
console.log(`Ready to serve in \x1b[33m${channelSize}\x1b[0m channels on \x1b[33m${guildSize}\x1b[0m servers.`);
console.log(`${client.readyAt}`);
console.log(`There is \x1b[33m${commandSize}\x1b[0m command loaded.`);
console.log(`Running Discord.js \x1b[33m${version}\x1b[0m`);
const ytdlpVersion = await new Promise((resolve, reject) => { 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) { if (err) {
reject(stderr); reject(stderr);
} }
@ -34,17 +19,24 @@ export default {
}); });
}); });
const commandSize = client.commands.size;
const clientTag = client.user.tag;
const guildSize = client.guilds.cache.size;
const channelSize = client.channels.cache.size;
const clientID = client.user.id;
console.log('===========[ READY ]===========');
console.log(`\x1b[32mLogged in as \x1b[34m${clientTag}\x1b[0m! (\x1b[33m${clientID}\x1b[0m)`);
console.log(`Ready to serve in \x1b[33m${channelSize}\x1b[0m channels on \x1b[33m${guildSize}\x1b[0m servers.`);
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 yt-dlp \x1b[33m${ytdlpVersion.replace('\n', '')}\x1b[0m`);
console.log('===========[ READY ]==========='); console.log('===========[ READY ]===========');
// If stats channel settings exist, send bot stats to it // If stats channel settings exist, send bot stats to it
if (statusChannel && NODE_ENV !== 'development') { if (statusChannel && NODE_ENV !== 'development') {
const channel = client.channels.resolve(statusChannel); const channel = client.channels.resolve(statusChannel);
channel.send( 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}`);
`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}`);
} }
}, },
}; };

View file

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

View file

@ -1,4 +1,6 @@
import https from 'node:https'; import https from 'node:https';
import dotenv from 'dotenv';
dotenv.config();
const { uptimeURL, uptimeInterval } = process.env; const { uptimeURL, uptimeInterval } = process.env;
export default { export default {

View file

@ -1,35 +1,26 @@
import fs from 'node:fs'; import fs from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url'; import { fileURLToPath } from 'node:url';
import { Client, Collection, GatewayIntentBits, Partials } from 'discord.js'; import { Client, Collection, Intents } from 'discord.js';
const { token, NODE_ENV } = process.env; import dotenv from 'dotenv';
dotenv.config();
const { token } = process.env;
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
const client = new Client({ const client = new Client({ intents: [Intents.FLAGS.GUILDS], shards: 'auto' });
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, GatewayIntentBits.GuildMessageReactions, GatewayIntentBits.GuildMembers],
partials: [Partials.Message, Partials.Reaction, Partials.Channel],
shards: 'auto',
});
// Load commands // Load commands
client.commands = new Collection(); client.commands = new Collection();
await loadCommandFromDir('fun');
fs.readdir(`${__dirname}/commands`, (err, categoryPath) => { await loadCommandFromDir('utility');
if (err) { await loadCommandFromDir('admin');
return console.error(err); await loadCommandFromDir('owner');
}
categoryPath.forEach(category => {
loadCommandFromDir(category);
});
});
// Load events // Load events
loadEventFromDir('client', client); await loadEventFromDir('client', client);
if (NODE_ENV !== 'development') { await loadEventFromDir('process', process);
loadEventFromDir('process', process);
}
client.login(token); client.login(token);
@ -39,13 +30,10 @@ async function loadCommandFromDir(dir) {
for (const file of commandFiles) { for (const file of commandFiles) {
const filePath = path.join(commandsPath, file); const filePath = path.join(commandsPath, file);
import(pathToFileURL(filePath)) let command = await import(filePath);
.then(importedCommand => { command = command.default;
const command = importedCommand.default;
client.commands.set(command.data.name, command); client.commands.set(command.data.name, command);
console.log(`Successfully loaded command \x1b[32m${command.category}/${command.data.name}\x1b[0m`);
})
.catch(error => console.error(`Failed to load command for path: ${filePath}`, error));
} }
} }
@ -55,16 +43,13 @@ async function loadEventFromDir(dir, listener) {
for (const file of eventFiles) { for (const file of eventFiles) {
const filePath = path.join(eventsPath, file); const filePath = path.join(eventsPath, file);
import(pathToFileURL(filePath)) let event = await import(filePath);
.then(importedEvent => { event = event.default;
const event = importedEvent.default;
if (event.once) { if (event.once) {
listener.once(event.name, (...args) => event.execute(...args, client)); listener.once(event.name, (...args) => event.execute(...args, client));
} }
else { else {
listener.on(event.name, (...args) => event.execute(...args, client)); listener.on(event.name, (...args) => event.execute(...args, client));
} }
})
.catch(error => console.error(`Failed to load event for path: ${filePath}`, error));
} }
} }

View file

@ -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."]

View file

@ -1 +0,0 @@
["baseball", "basketball", "football", "golf", "soccer", "tennis"]

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
["10 years later", "a day ago", "a day later", "a month ago", "a month later", "a week ago", "a week later", "a year ago", "a year later", "absentmindedly", "accidentally", "actively", "adventurously", "again", "all the time", "an hour ago", "an hour later", "anally", "angrily", "animatedly", "anxiously", "appreciatively", "ardently", "arrogantly", "artfully", "at sunrise", "at sunset", "at the full moon", "awkwardly", "barely", "beautifully", "billions of years ago", "bitterly", "biweekly", "blissfully", "boldly", "bravely", "brightly", "briskly", "buoyantly", "busily", "calmly", "carefully", "carelessly", "casually", "cautiously", "centenially", "certainly", "closely", "coaxingly", "coldly", "comfortably", "considerably", "continually", "cooly", "correctly", "courageously", "crossly", "crunchily", "curiously", "daily", "daintily", "deeply", "defiantly", "deliberately", "delicately", "deliciously", "delightedly", "densely", "determinedly", "diligently", "discreetly", "dreamily", "eagerly", "earnestly", "efficiently", "effortlessly", "elegantly", "energetically", "enthusiastically", "enviously", "every Tuesday", "every night", "every now and then", "evilly", "excessively", "excitedly", "expertly", "explosively", "extremely", "faithfully", "fearlessly", "ferociously", "fervently", "fiercely", "firmly", "five minutes later", "five years later", "fleetingly", "fluidly", "for 10 weeks", "for 36 hours", "forcibly", "forgivingly", "formally", "frantically", "frequently", "from now on", "generously", "genuinely", "gladly", "gleefully", "gloriously", "grandly", "gravely", "greatly", "greedily", "grudgingly", "grumpily", "happily", "hardly", "harmonically", "hastily", "hatefully", "heartily", "heavily", "hollowly", "honestly", "hungrily", "hurriedly", "illegally", "immediately", "imperfectly", "inappropriately", "indubitably", "instantly", "intensely", "intensively", "intentionally", "intently", "intuitively", "joyfully", "just 5 minutes ago", "justly", "keenly", "kindly", "laboriously", "later", "lightly", "loudly", "lovingly", "magestically", "magically", "majestically", "meekly", "mercifully", "merrily", "methodically", "mildly", "millions of years ago", "modestly", "mortally", "musically", "mysteriously", "naturally", "neatly", "necessarily", "never", "never again", "nimbly", "noisily", "nonchalantly", "now", "objectively", "obscenely", "occasionally", "often", "on Mondays", "once a month", "once a week", "once again", "once in a while", "outstandingly", "partially", "patiently", "peacefully", "perfectly", "perpetually", "persistently", "pointedly", "presently", "professionally", "profusely", "promptly", "proudly", "purposefully", "quickly", "quietly", "quite", "rapidly", "rashly", "readily", "really", "rebelliously", "recently", "recklessly", "regretfully", "repeatedly", "restlessly", "richly", "romantically", "roughly", "royally", "sadly", "saggingly", "sarcastically", "secretly", "several times", "shamefully", "sharply", "side to side", "silently", "single-handedly", "six months later", "slickly", "slimily", "sloppily", "slowly", "sluggishly", "smartly", "smoothly", "snugly", "so", "solemnly", "sometimes", "speedily", "spiritedly", "spontaneously", "stealthily", "strenuously", "strictly", "strongly", "stubbornly", "suspiciously", "sweetly", "sympathetically", "systematically", "terribly", "terrifyingly", "thickly", "thirstily", "this time", "thoroughly", "thoughtlessly", "ticklishly", "to kingdom come", "today", "tonight", "too", "torturously", "trillions of years ago", "truly", "unbelievably", "unnecessarily", "until further notice", "up and down", "urgently", "usually", "very", "victoriously", "vigilantly", "vigorously", "violently", "warily", "weakly", "wetly", "wholeheartedly", "wildly", "wisely", "with haste", "yesterday"]

View file

@ -1 +0,0 @@
["50 Cent", "Adam Sandler", "Alden Ehrenreich", "Anne Hathaway", "Arnold Schwarzenegger", "Axl Rose"," Ben Shapiro", "Ben Stein", "Ben Stiller", "Bernie Sanders", "Bill Murray", "Brad Pitt", "Channing Tatum", "Christian Bale", "Daniel Radcliffe", "Danny Masterson", "David Lee Roth", "Donald Trump", "Duff McKagan", "Eddie Van Halen", "Edward Norton", "Eminem", "Emma Stone", "Emma Watson", "Geoffery Lewis", "Hillary Clinton", "Hugh Jackson", "Jack Black", "Jake Epstein", "Jesse Eisenberg", "Jessica Alba", "John Cena", "John Travolta", "Johnny Depp", "Julia Garner", "Julia Roberts", "Justin Bieber", "Laura Prepon", "Lena Dunham", "Leonardo DiCaprio", "Marilyn Manson", "Megan Fox", "Michael Cera", "Morgan Freeman", "Natalie Portman", "Nicolas Cage", "Orlando Bloom", "Rihanna", "Robert Downey Jr.", "Robin Willians", "Ryan Seacrest", "Scarlett Johansson", "Serj Tankian", "Shia LaBeouf", "Slash", "Tom Cruise", "Tom Hanks", "Trent Reznor", "Will Ferrell", "Will Smith", "Woody Allen", "Zac Effron"]

View file

@ -1 +0,0 @@
["Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra", "Angola", "Anguilla", "Antarctica", "Antigua and Barbuda", "Argentina", "Armenia", "Aruba", "Ashmore and Cartier Islands", "Australia", "Austria", "Azerbaijan", "Bahrain", "Bangladesh", "Barbados", "Bassas da India", "Belarus", "Belgium", "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegovina", "Botswana", "Bouvet Island", "Brazil", "British Indian Ocean Territory", "British Virgin Islands", "Brunei", "Bulgaria", "Burkina Faso", "Burma", "Burundi", "Cambodia", "Cameroon", "Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China", "Christmas Island", "Clipperton Island", "Cocos Islands", "Colombia", "Comoros", "Cook Islands", "Coral Sea Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Czech Republic", "Democratic Republic of the Congo", "Denmark", "Dhekelia", "Djibouti", "Dominican Republic", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Europa Island", "Falkland Islands", "Faroe Islands", "Federated States of Micronesia", "Fiji", "Finland", "France", "French Guiana", "French Polynesia", "French Southern and Antarctic Lands", "Gabon", "Gaza Strip", "Georgia", "Germany", "Ghana", "Gibraltar", "Glorioso Islands", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea", "Guinea_Bissau", "Guyana", "Haiti", "Heard Island and McDonald Islands", "Holy See", "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland", "Isle of Man", "Israel", "Italy", "Jan Mayen", "Japan", "Jersey", "Jordan", "Juan de Nova Island", "Kazakhstan", "Kenya", "Kiribati", "Kuwait", "Kyrgyzstan", "Laos", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg", "Macau", "Macedonia", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique", "Mauritania", "Mauritius", "Mayotte", "Moldova", "Monaco", "Mongolia", "Montserrat", "Morocco", "Mozambique", "Namibia", "Nauru", "Navassa Island", "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger", "Nigeria", "Niue", "Norfolk Island", "North Korea", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau", "Panama", "Papua New Guinea", "Paracel Islands", "Paraguay", "Peru", "Philippines", "Pitcairn Islands", "Poland", "Portugal", "Puerto Rico", "Qatar", "Republic of the Congo", "Reunion", "Romania", "Russia", "Rwanda", "Saint Helena", "Saint Kitts and Nevis", "Saint Lucia", "Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino", "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia and Montenegro", "Seychelles", "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa", "South Georgia and the South Sandwich Islands", "South Korea", "Spain", "Spratly Islands", "Sri Lanka", "Sudan", "Suriname", "Svalbard", "Swaziland", "Sweden", "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "The Bahamas", "The Gambia", "Timor_Leste", "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tromelin Island", "Tunisia", "Turkey", "Turkmenistan", "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela", "Vietnam", "Virgin Islands", "Wake Island", "Wallis and Futuna", "West Bank", "Western Sahara", "Yemen", "Zambia", "Zimbabwe"]

View file

@ -1 +0,0 @@
["AIDS", "Alzheimer's", "Down syndrome", "Hepatitis A", "Hepatitis B", "Hepatitis C", "acute inclusion body encephalitis", "albinism", "alveolitis", "amnesia", "anal sacculitis", "anaphylaxis", "appendicitis", "arsenic poisoning", "arthritis", "athlete's foot", "autism", "bipolar disorder", "bronchitis", "deep vein thrombosis", "delirium", "dementia", "depression", "diarrhea", "dissociative identity disorder", "ebola", "epilepsy", "fibrosis", "folliculitis", "gastroenteritis", "halitosis", "herpes", "hyperthyroidism", "hypothyroidism", "influenza", "laryngitis", "lead poisoning", "lupus", "meningitis", "mental retardation", "mercury poisoning", "osteoporosis", "pinkeye", "plague", "rheumatoid arthritis", "sacculitis", "schizophrenia", "smallpox", "strep throat", "tetanus", "tonsilitis", "type I diabetes", "type II diabetes", "typhoid fever"]

View file

@ -1 +0,0 @@
["actinium", "aluminum", "americium", "antimony", "argon", "arsenic", "astatine", "barium", "berkelium", "beryllium", "bismusth", "boron", "bromine", "cadmium", "calcium", "californium", "carbon", "cerium", "cesium", "chlorine", "chromium", "cobalt", "copper", "curium", "dysprosium", "einsteinium", "erbium", "europium", "fermium", "fluorine", "francium", "gadolinium", "gallium", "germanium", "gold", "hafnium", "helium", "holmium", "hydrogen", "indium", "iodine", "iridium", "iron", "krypton", "lanthanum", "lawrencium", "lead", "lithium", "lutetium", "magnesium", "manganese", "mendelevium", "mercury", "molybdenum", "neodymium", "neon", "neptunium", "nickel", "niobium", "nitrogen", "nobelium", "osmium", "oxygen", "palladium", "phosphorus", "platinum", "plutonium", "polonium", "potassium", "praseodymium", "promethium", "protactinium", "radium", "radon", "rhenium", "rhodium", "rubidium", "ruthenium", "samarium", "scandium", "selenium", "silicon", "silver", "sodium", "strontium", "sulfur", "tantalum", "technetium", "tellurium", "terbium", "thallium", "thorium", "thulium", "tin", "titanium", "tungsten", "uranium", "vanadium", "xenon", "ytterbium", "yttrium", "zinc", "zircomium"]

View file

@ -1 +0,0 @@
["as if!", "no way!", "boo!", "please!", "oh my!", "that can't be!", "wow!", "oh boy...", "oh boy!", "oh dear", "ew!", "gross!", "ugh!", "big deal!", "whatever!", "I don't care!", "big whoop!", "it doesn't matter!", "get out!", "duh!", "no kidding!", "well, duh!", "well, yeah!", "you don't say?", "oh, no!", "finally!", "at last!", "at long last!", "about time!", "it's about time!", "yuck!", "yucky!", "ick!", "icky!", "rats!", "Jesus Christ!", "mother of god!", "amazing!", "damn it!"]

View file

@ -1 +0,0 @@
["hello", "hi", "hey", "greetings", "good day", "good morning", "good afternoon", "good evening"]

View file

@ -1 +0,0 @@
["PC building", "acting", "antique-shopping", "baking", "bank robbery", "baseball", "basket-weaving", "calligraphy", "camping", "cannibalism", "carving", "cooking", "cooking meth", "crocheting", "dancing", "deep-sea diving", "drawing", "drug dealing", "fantasy football", "farming", "filmmaking", "fishing", "flying", "gaming", "gardening", "glassblowing", "golf", "guitar", "hiking", "human dissection", "hunting", "knitting", "money laundering", "mushroom cultivation", "music composition", "opera singing", "painting", "partying", "piano", "poetry", "politics", "pottery", "programming", "pyrotechnics", "resurrecting the dead", "running", "sailing", "scuba diving", "sculpting", "sewing", "shopping", "singing", "skateboarding", "skydiving", "spearfishing", "taxidermy", "theater", "time travel", "toy collecting", "urban exploration", "web-browsing", "whittling", "writing"]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
["a", "anti", "auto", "bi", "circum", "contra", "endo", "exo", "extra", "fore", "homo", "hyper", "in", "intra", "mega", "mid", "mini", "mis", "mono", "non", "octo", "omni", "over", "penta", "post", "pre", "pseudo", "psycho", "pyro", "quad", "semi", "sub", "super", "trans", "tri", "un", "under", "uni"]

View file

@ -1 +0,0 @@
["he", "it", "she", "they"]

View file

@ -1 +0,0 @@
["Alabama", "Alaska", "Arizona", "Arkansas", "Baden-W\u00fcrttemberg", "Bavaria", "Berlin", "Brandenburg", "Bremen", "California", "Colorado", "Connecticut", "Delaware", "Florida", "Georgia", "Hamburg", "Hawaii", "Hesse", "Idaho", "Illinois", "Indiana", "Iowa", "Kansas", "Kentucky", "Louisiana", "Lower Saxony", "Maine", "Maryland", "Massachusetts", "Mecklenburg-Vorpommern", "Michigan", "Minnesota", "Mississippi", "Missouri", "Montana", "Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico", "New York", "North Carolina", "North Dakota", "North Rhine-Westphalia", "Ohio", "Oklahoma", "Oregon", "Pennsylvania", "Rhineland-Palatinate", "Rhode Island", "Saarland", "Saxony", "Saxony-Anhalt", "Schleswig-Holstein", "South Carolina", "South Dakota", "Tennessee", "Texas", "Thuringia", "Utah", "Vermont", "Virginia", "Washington", "West Virginia", "Wisconsin", "Wyoming"]

View file

@ -1 +0,0 @@
["Colonel", "Daddy", "Dojo", "Dr.", "Governor", "Granny", "Honorable", "King", "Madam", "Mama", "Master", "Mayor", "Mistress", "Moist", "Mr.", "Mrs.", "Ms", "Old", "Papa", "Prince", "Professor", "Queen", "Sensei", "Sergeant", "Sir"]

View file

@ -1 +0,0 @@
["bucket", "centimeter", "century", "cubic centimeter", "cup", "day", "foot", "gallon", "gram", "handful", "hour", "inch", "joule", "kilogram", "kilojoule", "kilometer", "kilovolt", "kilowatt", "light-year", "liter", "megaton", "megawatt", "meter", "microfarad", "mile", "milliampere", "milliliter", "millisecond", "millivolt", "milliwatt", "minute", "month", "mouthful", "ounce", "pint", "pound", "quart", "second", "tablespoon", "teaspoon", "ton", "yard", "year"]

View file

@ -1 +0,0 @@
["abduct", "abolish", "align", "amend", "amputate", "annihilate", "announce", "appreciate", "apprehend", "articulate", "assault", "associate", "attack", "authenticate", "bake", "bang", "barbeque", "bathe", "bawl", "beep", "behead", "belly-flop", "bind", "bite", "blacken", "blast", "bleed", "bless", "bloat", "bloom", "blossom", "blow", "bludgeon", "boil", "breastfeed", "breathe", "bubble", "burn", "burp", "cackle", "call", "caress", "chew", "chill", "choke", "chomp", "chop", "chow", "churn", "clean", "click", "clip", "commandeer", "conserve", "convict", "cook", "crack", "cram", "crank", "crash", "crawl", "cremate", "cripple", "croak", "crouch", "crumple", "crunch", "crush", "cry", "cuddle", "cultivate", "customize", "cut", "cut in half", "dangle", "darken", "decapitate", "declare", "decorate", "deep-fry", "defecate", "defy", "deny", "despise", "dice", "dig", "discipline", "dishonor", "dislike", "dismantle", "dissect", "dominate", "donate", "draft", "drain", "dramatize", "drip", "dry-freeze", "dye", "eat", "ejaculate", "eject", "elect", "electrocute", "eliminate", "embrace", "enforce", "examine", "exclaim", "explode", "exploit", "extend", "extrapolate", "extrude", "fabricate", "fall apart", "falsify", "fart", "feed", "fester", "fiddle", "fight", "fistfight", "fizz", "flap", "flash", "flatten", "flick", "floss", "flutter", "fly", "force", "forecast", "freeze", "froth", "fume", "gallop", "gargle", "gleam", "glorify", "glow", "glue", "gnaw", "gouge", "grab", "grate", "grind", "grip", "groom", "grow", "grunt", "gulp", "guzzle", "gyrate", "hack", "hammer", "handle", "hang", "harass", "harden", "headbutt", "hiccup", "hiss", "hog-tie", "hoist", "hoot", "hug", "hunt", "hurl", "hypnotize", "impale", "impeach", "impede", "implode", "inaugurate", "infest", "ingest", "inject", "injure", "invigorate", "iron", "jargogle", "jerk", "jet-spray", "jimmy", "jingle", "joust", "kick", "kidnap", "kill", "kiss", "knead", "knock out", "lather", "laugh", "lay", "lecture", "legalize", "lick", "lighten", "like", "liquefy", "liquidate", "loathe", "loosen", "maim", "make fun of", "mangle", "manhandle", "manipulate", "march", "marinate", "massage", "masticate", "maul", "mist", "misuse", "moan", "moisten", "move", "mumble", "mutilate", "mutter", "nab", "nail", "need", "nip", "obfuscate", "oil", "organize", "paint", "palpitate", "penetrate", "pepper", "petition", "pickle", "pierce", "pillage", "pinch", "piss", "plaster", "pluck", "plunge", "poke", "polish", "pop", "pour", "pray", "preen", "press", "probe", "prod", "prosecute", "prove", "puff", "pull", "pump", "punch", "punish", "purify", "push", "put up with", "quaff", "quantify", "radiate", "ram", "rapture", "ratify", "rattle", "rearrange", "recycle", "report", "ride", "rip", "ripple", "rise", "roar", "rob", "roll", "rot", "rub", "run", "run over", "run after", "rustle", "salt", "sanitize", "savor", "say", "scold", "scorch", "scratch", "scream", "screech", "screw", "scrub", "scrunch", "sculpt", "season", "serve", "set on fire", "shake", "sharpen", "shatter", "shave", "shimmer", "shine", "shiver", "shoot", "shout", "shove", "shower", "shred", "shriek", "shrink", "shudder", "sit", "skewer", "skip", "slam", "slap", "slash", "slay", "sleepwalk", "slip", "slit", "slouch", "slurp", "smack", "smear", "smoke", "smolder", "snap", "snicker", "sniff", "snip", "snoop", "snort", "snuffle", "snuggle", "soak", "soften", "solidify", "spank", "sparkle", "spelunk", "spit", "splash", "splatter", "spray", "sprinkle", "sprint", "spurt", "sputter", "square dance", "squat", "squeal", "squeeze", "squelch", "squirt", "squish", "stab", "stampede", "stand", "staple", "steam", "stew", "stick", "stimulate", "stir", "stomp", "strain", "strangle", "strap", "strategize", "streamline", "stress-test", "strike", "strut", "stuff", "suckle", "sue", "superglue", "swallow", "swear", "swipe", "tap", "tape", "throttle", "throw", "tickle", "tighten", "tinkle", "tiptoe", "toast", "toke", "tongue", "touch", "transcribe", "tremble", "trot", "tune", "twang", "twinkle", "twist", "undermine", "undress", "undulate", "uproot", "urinate", "vaporize", "venerate", "verify", "veto", "vibrate", "vomit", "waddle", "wail", "walk", "want", "waste", "wave", "wedge", "whimper", "whine", "whip", "whisper", "whiten", "wiggle", "wilt", "withdraw", "wrinkle", "yank", "yell", "zip"]

View file

@ -1 +0,0 @@
["yes", "yeah", "sure", "indeed", "affirmative", "absolutely", "yup", "yep", "no", "nope", "nah", "negative", "absolutely not"]

View file

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

View file

@ -1,27 +0,0 @@
'use strict';
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.createTable('optouts', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
userID: {
type: Sequelize.BIGINT
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
async down(queryInterface, Sequelize) {
await queryInterface.dropTable('optouts');
}
};

View file

@ -1,23 +0,0 @@
'use strict';
const {
Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class optout extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate(models) {
// define association here
}
}
optout.init({
userID: DataTypes.BIGINT
}, {
sequelize,
modelName: 'optout',
});
return optout;
};

6346
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -4,9 +4,9 @@
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"start": "node --env-file .env .", "start": "node .",
"deploy": "node --env-file .env scripts/deploy-commands.js", "deploy": "node deploy-commands.cjs",
"deployGlobally": "node --env-file .env scripts/deploy-commands.js global", "deployGlobally": "node deploy-commands.cjs global",
"lint": "eslint .", "lint": "eslint .",
"lintfix": "eslint . --fix", "lintfix": "eslint . --fix",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
@ -17,25 +17,23 @@
"homepage": "https://libtar.de", "homepage": "https://libtar.de",
"license": "AGPL", "license": "AGPL",
"dependencies": { "dependencies": {
"@discordjs/rest": "^2.3.0", "@discordjs/builders": "^0.13.0",
"discord-api-types": "^0.37.91", "@discordjs/rest": "^0.4.1",
"discord.js": "^14.15.3", "discord-api-types": "^0.33.1",
"mariadb": "^3.3.1", "discord.js": "^13.7.0",
"node-fetch": "^3.3.2", "dotenv": "^16.0.1",
"sequelize": "^6.37.3", "mariadb": "^3.0.1",
"turndown": "^7.2.0", "mysql2": "^2.3.3",
"twitter-api-v2": "^1.17.1", "node-fetch": "^3.2.6",
"ytpplus-node": "github:Supositware/ytpplus-node" "sequelize": "^6.21.3",
"turndown": "^7.1.1",
"twit": "^2.2.11"
}, },
"devDependencies": { "devDependencies": {
"@babel/eslint-parser": "^7.18.9", "@babel/eslint-parser": "^7.18.9",
"@babel/plugin-syntax-import-assertions": "^7.18.6", "@babel/plugin-syntax-import-assertions": "^7.18.6",
"@eslint/eslintrc": "^3.1.0",
"@eslint/js": "^9.6.0",
"@types/node": "^18.7.3", "@types/node": "^18.7.3",
"eslint": "^8.57.0", "eslint": "^8.16.0",
"globals": "^15.8.0", "sequelize-cli": "^6.4.1"
"sequelize-cli": "^6.4.1",
"sqlite3": "^5.1.7"
} }
} }

29
prereq.cjs Normal file
View file

@ -0,0 +1,29 @@
const fs = require('node:fs');
const https = require('node:https');
console.log('Downloading latest version of yt-dlp');
const downloadUrl = 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp';
download(downloadUrl);
function download(url) {
https.get(url, (res) => {
if (res.statusCode === 301 || res.statusCode === 302) {
console.log(`yt-dlp download url: ${res.headers.location}`);
return download(res.headers.location);
}
const path = './bin/yt-dlp';
const filePath = fs.createWriteStream(path);
res.pipe(filePath);
filePath.on('finish', () => {
filePath.close();
fs.chmodSync('./bin/yt-dlp', '755');
console.log('yt-dlp download finished.');
});
filePath.on('error', (err) => {
filePath.close();
console.error(err.message);
});
});
}

View file

@ -11,12 +11,12 @@ These instructions will get you a copy of the project up and running on your loc
You need to install the following You need to install the following
* ffmpeg & ffprobe (Optional but very recommanded: for yt-dlp to merge video/audio formats and Handbrake to compress videos.) * ffmpeg (Optional but very recommanded: for yt-dlp to merge video/audio formats)
* yt-dlp ([a file can download it for you](scripts/updateytdlp.js)) * yt-dlp ([a file can download it for you](prereq.cjs))
* HandBrakeCLI (For [download](commands/utility/download.js)) * HandBrakeCLI (For [download](commands/utility/download.js))
* gifsicle (For [vid2gif](commands/utility/vid2gif.js)) * gifsicle (For [vid2gif](commands/utility/vid2gif.js))
* gifki (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 ### Installing
``` ```
@ -27,10 +27,10 @@ npm install
``` ```
To run the bot for the first time you need to execute [deploy-commands.js](scripts/deploy-commands.js) so the commands can be registered, don't forget to set your .env accordingly. To run the bot for the first time you need to execute [deploy-commands.js](scripts/deploy-commands.js) so the commands can be registered, don't forget to set your .env accordingly.
``node --env-file .env scripts/deploy-commands.cjs`` ``node scripts/deploy-commands.cjs``
then you can just run it normally. then you can just run it normally.
``node --env-file .env index.js`` ``node index.js``
If you want to run the bot automatically you can use pm2 If you want to run the bot automatically you can use pm2
``` ```
@ -38,7 +38,7 @@ npm install -g pm2
pm2 start index.js --name (insert name) pm2 start index.js --name (insert name)
``` ```
If you are on linux and don't need automatic restart on crash you can just do If you are on linux and don't need automatic restart on crash you can just do
``nohup node --env-file .env index.js &`` ``nohup node index.js &``
## Built With ## Built With

134
scripts/deploy-commands.cjs Normal file
View file

@ -0,0 +1,134 @@
const { SlashCommandBuilder } = require('@discordjs/builders');
const { REST } = require('@discordjs/rest');
const { Routes } = require('discord-api-types/v9');
require('dotenv').config();
const { clientId, guildId, token } = process.env;
const commands = [
new SlashCommandBuilder()
.setName('ping')
.setDescription('Replies with pong!'),
new SlashCommandBuilder()
.setName('download')
.setDescription('Download a video.')
.addStringOption(option =>
option.setName('url')
.setDescription('url of the video you want to download.')
.setRequired(true))
.addBooleanOption(option =>
option.setName('format')
.setDescription('Choose the quality of the video.')
.setRequired(false))
.addBooleanOption(option =>
option.setName('compress')
.setDescription('Compress the video?')
.setRequired(false)),
new SlashCommandBuilder()
.setName('reddit')
.setDescription('Send random images from the subreddit you choose')
.addStringOption(option =>
option.setName('subreddit')
.setDescription('The subreddit you wish to see')
.setRequired(true)),
new SlashCommandBuilder()
.setName('vid2gif')
.setDescription('Convert your video into a gif.')
.addStringOption(option =>
option.setName('url')
.setDescription('URL of the video you want to convert')
.setRequired(true)),
new SlashCommandBuilder()
.setName('feedback')
.setDescription('Send a feedback to the developer.')
.addStringOption(option =>
option.setName('feedback')
.setDescription('The message you want to send me.')
.setRequired(true)),
new SlashCommandBuilder()
.setName('inspirobot')
.setDescription('Get an image from inspirobot'),
new SlashCommandBuilder()
.setName('tweet')
.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('The content of the tweet you want to send me.')
.setRequired(false))
.addAttachmentOption(option =>
option.setName('image')
.setDescription('Optional attachment (Image only.)')
.setRequired(false)),
new SlashCommandBuilder()
.setName('4chan')
.setDescription('Send random images from a 4chan board of your choosing!')
.addStringOption(option =>
option.setName('board')
.setDescription('The board you wish to see')
.setRequired(true)),
new SlashCommandBuilder()
.setName('donator')
.setDescription('All the people who donated for this bot <3'),
new SlashCommandBuilder()
.setName('donate')
.setDescription('Show donation link for the bot.'),
new SlashCommandBuilder()
.setName('about')
.setDescription('About me (The bot)'),
new SlashCommandBuilder()
.setName('stats')
.setDescription('Show some stats about the bot'),
new SlashCommandBuilder()
.setName('fakeuser')
.setDescription('Fake a user with webhooks')
.addMentionableOption(option =>
option.setName('user')
.setDescription('Who do you want to fake?')
.setRequired(true))
.addStringOption(option =>
option.setName('message')
.setDescription('What message do you want me to send?')
.setRequired(true))
.addAttachmentOption(option =>
option.setName('image')
.setDescription('Optional attachment (Image only.)')
.setRequired(false)),
new SlashCommandBuilder()
.setName('s')
.setDescription('What could this be 🤫')
.addStringOption(option =>
option.setName('something')
.setDescription('🤫')
.setRequired(true)),
]
.map(command => command.toJSON());
const rest = new REST({ version: '9' }).setToken(token);
if (process.argv[2] === 'global') {
rest.put(Routes.applicationCommands(clientId), { body: commands })
.then(() => console.log('Successfully registered application commands globally.'))
.catch(console.error);
}
else if (process.argv[2] === 'delete') {
rest.put(Routes.applicationGuildCommands(clientId, guildId), { body: [] })
.then(() => console.log('Successfully deleted all guild commands.'))
.catch(console.error);
}
rest.put(Routes.applicationGuildCommands(clientId, guildId), { body: commands })
.then(() => console.log(`Successfully registered application commands for the guild ${guildId}.`))
.catch(console.error);

View file

@ -1,42 +0,0 @@
import { REST } from '@discordjs/rest';
import { Routes } from 'discord-api-types/v9';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
const { clientId, guildId, token } = process.env;
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const commands = [];
const categoryPath = fs.readdirSync(`${__dirname}/../commands`);
for (let i = 0; i < categoryPath.length; i++) {
const commandsPath = path.join(`${__dirname}/../commands`, categoryPath[i]);
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
for (const file of commandFiles) {
const filePath = path.join(commandsPath, file);
const command = await import(pathToFileURL(filePath));
if (command.default.integration_types) {
Object.assign(command.default.data, { integration_types: command.default.integration_types });
Object.assign(command.default.data, { contexts: [0, 1, 2] });
}
commands.push(command.default.data.toJSON());
}
}
const rest = new REST({ version: '9' }).setToken(token);
if (process.argv[2] === 'global') {
rest.put(Routes.applicationCommands(clientId), { body: commands })
.then(() => console.log('Successfully registered application commands globally.'))
.catch(console.error);
}
else if (process.argv[2] === 'delete') {
rest.put(Routes.applicationGuildCommands(clientId, guildId), { body: [] })
.then(() => console.log('Successfully deleted all guild commands.'))
.catch(console.error);
}
rest.put(Routes.applicationGuildCommands(clientId, guildId), { body: commands })
.then(() => console.log(`Successfully registered application commands for the guild ${guildId}.`))
.catch(console.error);

View file

@ -0,0 +1,42 @@
const { SlashCommandBuilder } = require('@discordjs/builders');
const { REST } = require('@discordjs/rest');
const { Routes } = require('discord-api-types/v9');
require('dotenv').config();
const { clientId, guildId, token } = process.env;
const commands = [
new SlashCommandBuilder()
.setName('die')
.setDescription('Kill the bot'),
new SlashCommandBuilder()
.setName('ublacklist')
.setDescription('Blacklist a user from the bot')
.addStringOption(option =>
option.setName('command')
.setDescription('Which command do you want to get a user blacklisted from?')
.setRequired(true))
.addStringOption(option =>
option.setName('userid')
.setDescription('Who do you want to blacklist?')
.setRequired(true))
.addStringOption(option =>
option.setName('reason')
.setDescription('The reason of the blacklist.')
.setRequired(false)),
new SlashCommandBuilder()
.setName('deletewteet')
.setDescription('Delete a tweet')
.addStringOption(option =>
option.setName('tweetid')
.setDescription('The id of the tweet you wish to delete.')
.setRequired(true)),
]
.map(command => command.toJSON());
const rest = new REST({ version: '9' }).setToken(token);
rest.put(Routes.applicationGuildCommands(clientId, guildId), { body: commands })
.then(() => console.log(`Successfully registered application commands for the guild ${guildId}.`))
.catch(console.error);

View file

@ -1,34 +0,0 @@
import fs from 'node:fs';
import https from 'node:https';
export default {
download,
};
async function download(url, output) {
return new Promise((resolve, reject) => {
https.get(url, (res) => {
if (res.statusCode === 301 || res.statusCode === 302) {
console.log(`${output} download url: ${res.headers.location}`);
return download(res.headers.location, output);
}
const path = output;
const tmpPath = `${output}.new`;
const filePath = fs.createWriteStream(tmpPath);
res.pipe(filePath);
filePath.on('finish', () => {
filePath.close();
fs.renameSync(tmpPath, path);
fs.chmodSync(path, '755');
console.log(`${url} download finished.`);
return resolve(true);
});
filePath.on('error', (err) => {
filePath.close();
return reject(err);
});
});
});
}

View file

@ -1,27 +0,0 @@
import fetch from 'node-fetch';
import { Client, GatewayIntentBits } from 'discord.js';
const { botsggToken, botsggEndpoint, token } = process.env;
const client = new Client({
intents: [GatewayIntentBits.Guilds],
});
await client.login(token);
const body = {
guildCount: client.guilds.cache.size,
};
console.log(body);
const response = await fetch(`${botsggEndpoint}/bots/${client.user.id}/stats`, {
method: 'post',
body: JSON.stringify(body),
headers: { 'Authorization': botsggToken, 'Content-Type': 'application/json' },
});
const data = await response.json();
console.log(data);
process.exit();

View file

@ -1,9 +1,8 @@
// 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 fs from 'node:fs';
import utils from './downloadutils.js'; import https from 'node:https';
(async () => {
if (process.platform !== 'linux' && process.argv[2] !== '-f') { 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'); 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); process.exit(1);
} }
else if (process.platform !== 'linux' && process.argv[2] === '-f') { else if (process.platform !== 'linux' && process.argv[2] === '-f') {
@ -12,6 +11,31 @@ import utils from './downloadutils.js';
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'); download(downloadUrl);
async function download(url) {
return new Promise((resolve, reject) => {
https.get(url, (res) => {
if (res.statusCode === 301 || res.statusCode === 302) {
console.log(`yt-dlp download url: ${res.headers.location}`);
return download(res.headers.location);
}
const tmpPath = './bin/yt-dlp.new';
const path = './bin/yt-dlp';
const filePath = fs.createWriteStream(tmpPath);
res.pipe(filePath);
filePath.on('finish', () => {
filePath.close();
fs.renameSync(tmpPath, path);
fs.chmodSync('./bin/yt-dlp', '755');
console.log('yt-dlp download finished.');
resolve(true);
}); });
filePath.on('error', (err) => {
filePath.close();
reject(err);
});
});
});
}

Some files were not shown because too many files have changed in this diff Show more