forked from Supositware/Haha-Yes
Compare commits
120 commits
Author | SHA1 | Date | |
---|---|---|---|
1ea8a6f26e | |||
d18275cd7a | |||
7926820e70 | |||
126d29fdf9 | |||
64c45f87c0 | |||
a5cc9c45ed | |||
7ad4618dd0 | |||
705921b8d0 | |||
0b12e2d496 | |||
4f84a09a7e | |||
d604f0ad8e | |||
19855f82f0 | |||
9855987cbc | |||
c0507dc981 | |||
26db60e95f | |||
6983ea58b7 | |||
6ce2faf21b | |||
97254f619c | |||
23bcd036c0 | |||
34ab603462 | |||
4c5d879650 | |||
fe641132da | |||
8c6b06a3d0 | |||
4cf0d0bac1 | |||
77a5ac6137 | |||
a92b16fba4 | |||
c3fd22f02f | |||
2afbca10ec | |||
5e012e0701 | |||
3f28a897b4 | |||
4a236497bd | |||
09a180e36e | |||
0d4b88c465 | |||
49c756bd62 | |||
50b55edae3 | |||
281edd2d1d | |||
62666e2a3f | |||
427d3449d5 | |||
a86044c7f6 | |||
ec08c4fa80 | |||
454f2c4296 | |||
c44ae79640 | |||
5d0d9bce08 | |||
7d1151e6ce | |||
2a8356d219 | |||
e4b441e5f5 | |||
89f00a2fcf | |||
e6dca692ed | |||
12ba7621b6 | |||
bf9c87f3b8 | |||
2c25890f5b | |||
ceafee8287 | |||
76f7e40d8f | |||
35b57c219f | |||
d96d32f008 | |||
fe014ca7d7 | |||
995634a4b2 | |||
3464736d85 | |||
fd7ca30e1c | |||
38b8f80c43 | |||
ff8c6c29b0 | |||
44a629c7fc | |||
28ff4f518e | |||
e8fc57394f | |||
3b9d2dc556 | |||
b095d5ce3a | |||
da3e0185e1 | |||
50e49db47c | |||
591652f33f | |||
49e13885fe | |||
0bde6afdce | |||
c782708fa6 | |||
1cd6a6009d | |||
520ca95b29 | |||
ff98b259e7 | |||
fa4b5165e8 | |||
162a91ca48 | |||
5d6746a233 | |||
16842d7127 | |||
0c38de9ea2 | |||
e235f064d8 | |||
8546fb30f5 | |||
ba42ef6f37 | |||
b559edcd10 | |||
1269403787 | |||
de6e0dd3c7 | |||
4e5324155d | |||
65eb5b997f | |||
fb4db75f09 | |||
59bf0b9430 | |||
543ab35c9e | |||
88ff7390cd | |||
ccf9dc5785 | |||
5cc94e54a3 | |||
d925e62004 | |||
633f0a6fec | |||
780aef27c5 | |||
408176cc9d | |||
cd4ffa8b53 | |||
aacd7aa9fa | |||
f294e8cee1 | |||
3780fad9ae | |||
d4e3693be6 | |||
d926931e37 | |||
6a9425eccc | |||
1991925213 | |||
0f72e8c180 | |||
64988e340f | |||
bd4dcd087e | |||
c68d4fca00 | |||
56d06cedc4 | |||
1585941e8a | |||
646421df8f | |||
cb13a55c0c | |||
2f34b9fcc8 | |||
dceb4fbbb8 | |||
39ff404deb | |||
2cc13e8328 | |||
fce229e73a | |||
32fb7bc005 |
58 changed files with 2363 additions and 4175 deletions
|
@ -2,7 +2,7 @@ token=YourToken
|
||||||
clientId=BotClientId
|
clientId=BotClientId
|
||||||
guildId=DevGuildId
|
guildId=DevGuildId
|
||||||
ownerId=OwnerUserId
|
ownerId=OwnerUserId
|
||||||
statusChannel=
|
statusChannel=CHannelIdForStatus
|
||||||
uptimeURL=UptimeKumaOrWhateverStatusThingYouUseOrJustLeaveEmpty
|
uptimeURL=UptimeKumaOrWhateverStatusThingYouUseOrJustLeaveEmpty
|
||||||
uptimeInterval=60
|
uptimeInterval=60
|
||||||
twiConsumer=TwitterConsumerToken
|
twiConsumer=TwitterConsumerToken
|
||||||
|
@ -14,4 +14,7 @@ twiLogChannel=ChannelWhereTheDetailedInfoOfTheCommandIsSent
|
||||||
botsggToken=APITokenForBots.gg
|
botsggToken=APITokenForBots.gg
|
||||||
botsggEndpoint=https://discord.bots.gg/api/v1
|
botsggEndpoint=https://discord.bots.gg/api/v1
|
||||||
stableHordeApi=0000000000
|
stableHordeApi=0000000000
|
||||||
stableHordeID=0000
|
stableHordeID=0000
|
||||||
|
NODE_ENV=development
|
||||||
|
ytdlpMaxResolution=720
|
||||||
|
proxy=socks5://localhost:3128
|
|
@ -1,57 +0,0 @@
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
2
.github/ISSUE_TEMPLATE/bug.md
vendored
2
.github/ISSUE_TEMPLATE/bug.md
vendored
|
@ -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 feature request. -->
|
- [ ] Yes <!-- If you have to put yes you don't need to submit that bug report. -->
|
||||||
- [ ] No
|
- [ ] No
|
||||||
|
|
13
.gitignore
vendored
13
.gitignore
vendored
|
@ -1,7 +1,16 @@
|
||||||
.env
|
.env
|
||||||
node_modules/
|
node_modules/
|
||||||
bin/
|
|
||||||
config/config.json
|
config/config.json
|
||||||
json/board/
|
json/board/
|
||||||
unloaded/
|
unloaded/
|
||||||
database.sqlite3
|
database.sqlite3
|
||||||
|
tmp/*.js
|
||||||
|
|
||||||
|
bin/yt-dlp*
|
||||||
|
bin/HandBrakeCLI*
|
||||||
|
bin/upload.sh
|
||||||
|
bin/dectalk
|
||||||
|
|
||||||
|
asset/ytp/sources
|
||||||
|
asset/ytp/music
|
||||||
|
asset/ytp/sounds
|
0
bin/.keep
Normal file
0
bin/.keep
Normal file
|
@ -27,6 +27,7 @@ export default {
|
||||||
.setDescription('What do you want the AI to generate?')
|
.setDescription('What do you want the AI to generate?')
|
||||||
.setRequired(true)),
|
.setRequired(true)),
|
||||||
category: 'AI',
|
category: 'AI',
|
||||||
|
alias: ['i2i'],
|
||||||
async execute(interaction, args, client) {
|
async execute(interaction, args, client) {
|
||||||
await interaction.deferReply();
|
await interaction.deferReply();
|
||||||
|
|
||||||
|
@ -77,6 +78,12 @@ async function generate(i, prompt, client, b64Img) {
|
||||||
let response = await fetch('https://stablehorde.net/api/v2/generate/async', fetchParameters);
|
let response = await fetch('https://stablehorde.net/api/v2/generate/async', fetchParameters);
|
||||||
|
|
||||||
response = await response.json();
|
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 wait_time = 5000;
|
||||||
let checkURL = `https://stablehorde.net/api/v2/generate/check/${response.id}`;
|
let checkURL = `https://stablehorde.net/api/v2/generate/check/${response.id}`;
|
||||||
const checking = setInterval(async () => {
|
const checking = setInterval(async () => {
|
||||||
|
@ -84,8 +91,13 @@ async function generate(i, prompt, client, b64Img) {
|
||||||
|
|
||||||
if (checkResult === undefined) return;
|
if (checkResult === undefined) return;
|
||||||
if (!checkResult.done) {
|
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) {
|
if (checkResult.wait_time < 0) {
|
||||||
console.log(checkResult);
|
console.log(checkResult.raw);
|
||||||
clearInterval(checking);
|
clearInterval(checking);
|
||||||
return i.editReply({ content: 'No servers are currently available to fulfill your request, please try again later.' });
|
return i.editReply({ content: 'No servers are currently available to fulfill your request, please try again later.' });
|
||||||
}
|
}
|
||||||
|
@ -116,21 +128,14 @@ async function generate(i, prompt, client, b64Img) {
|
||||||
const row = new ActionRowBuilder()
|
const row = new ActionRowBuilder()
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(`regenerate${i.user.id}`)
|
.setCustomId(`regenerate${i.user.id}${i.id}`)
|
||||||
.setLabel('🔄 Regenerate')
|
.setLabel('🔄 Regenerate')
|
||||||
.setStyle(ButtonStyle.Primary),
|
.setStyle(ButtonStyle.Primary),
|
||||||
);
|
);
|
||||||
|
|
||||||
await i.editReply({ embeds: [stableEmbed], components: [row], files: [generatedImg] });
|
await i.editReply({ embeds: [stableEmbed], components: [row], files: [generatedImg] });
|
||||||
|
|
||||||
client.once('interactionCreate', async (interactionMenu) => {
|
listenButton(client, i, prompt);
|
||||||
if (i.user !== interactionMenu.user) return;
|
|
||||||
if (!interactionMenu.isButton) return;
|
|
||||||
if (interactionMenu.customId === `regenerate${interactionMenu.user.id}`) {
|
|
||||||
await interactionMenu.deferReply();
|
|
||||||
await generate(interactionMenu, prompt, client);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, wait_time);
|
}, wait_time);
|
||||||
}
|
}
|
||||||
|
@ -140,14 +145,27 @@ async function checkGeneration(url) {
|
||||||
check = await check.json();
|
check = await check.json();
|
||||||
|
|
||||||
if (!check.is_possible) {
|
if (!check.is_possible) {
|
||||||
return { done: false, wait_time: -1 };
|
return { done: false, wait_time: -1, raw: check };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (check.done) {
|
if (check.done) {
|
||||||
if (!check.generations) {
|
if (!check.generations) {
|
||||||
return { done: false, wait_time: check.wait_time * 1000 };
|
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 };
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ export default {
|
||||||
.setDescription('What do you want the AI to generate?')
|
.setDescription('What do you want the AI to generate?')
|
||||||
.setRequired(true)),
|
.setRequired(true)),
|
||||||
category: 'AI',
|
category: 'AI',
|
||||||
|
alias: ['t2i'],
|
||||||
async execute(interaction, args, client) {
|
async execute(interaction, args, client) {
|
||||||
await interaction.deferReply();
|
await interaction.deferReply();
|
||||||
generate(interaction, args.prompt, client);
|
generate(interaction, args.prompt, client);
|
||||||
|
@ -101,21 +102,14 @@ async function generate(i, prompt, client) {
|
||||||
const row = new ActionRowBuilder()
|
const row = new ActionRowBuilder()
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(`regenerate${i.user.id}`)
|
.setCustomId(`regenerate${i.user.id}${i.id}`)
|
||||||
.setLabel('🔄 Regenerate')
|
.setLabel('🔄 Regenerate')
|
||||||
.setStyle(ButtonStyle.Primary),
|
.setStyle(ButtonStyle.Primary),
|
||||||
);
|
);
|
||||||
|
|
||||||
await i.editReply({ embeds: [stableEmbed], components: [row], files: [generatedImg] });
|
await i.editReply({ embeds: [stableEmbed], components: [row], files: [generatedImg] });
|
||||||
|
|
||||||
client.once('interactionCreate', async (interactionMenu) => {
|
listenButton(client, i, prompt);
|
||||||
if (i.user !== interactionMenu.user) return;
|
|
||||||
if (!interactionMenu.isButton) return;
|
|
||||||
if (interactionMenu.customId === `regenerate${interactionMenu.user.id}`) {
|
|
||||||
await interactionMenu.deferReply();
|
|
||||||
await generate(interactionMenu, prompt, client);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, wait_time);
|
}, wait_time);
|
||||||
}
|
}
|
||||||
|
@ -136,3 +130,16 @@ async function checkGeneration(url) {
|
||||||
return { done: true, image: check.generations[0].img, seed: check.generations[0].seed, worker_id: check.generations[0].worker_id, worker_name: check.generations[0].worker_name };
|
return { done: true, image: check.generations[0].img, seed: check.generations[0].seed, worker_id: check.generations[0].worker_id, worker_name: check.generations[0].worker_name };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function listenButton(client, interaction, prompt) {
|
||||||
|
client.once('interactionCreate', async (interactionMenu) => {
|
||||||
|
if (!interactionMenu.isButton()) return;
|
||||||
|
|
||||||
|
await interactionMenu.update({ components: [] });
|
||||||
|
|
||||||
|
if (interactionMenu.customId === `regenerate${interactionMenu.user.id}${interaction.id}`) {
|
||||||
|
await interactionMenu.deferReply();
|
||||||
|
await generate(interactionMenu, prompt, client);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -19,13 +19,13 @@ export default {
|
||||||
const row = new ActionRowBuilder()
|
const row = new ActionRowBuilder()
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(`yes${interaction.user.id}`)
|
.setCustomId(`yes${interaction.user.id}${interaction.id}`)
|
||||||
.setLabel('Yes')
|
.setLabel('Yes')
|
||||||
.setStyle(ButtonStyle.Primary),
|
.setStyle(ButtonStyle.Primary),
|
||||||
)
|
)
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(`no${interaction.user.id}`)
|
.setCustomId(`no${interaction.user.id}${interaction.id}`)
|
||||||
.setLabel('No')
|
.setLabel('No')
|
||||||
.setStyle(ButtonStyle.Danger),
|
.setStyle(ButtonStyle.Danger),
|
||||||
);
|
);
|
||||||
|
@ -39,18 +39,24 @@ export default {
|
||||||
return interaction.editReply({ content: 'Auto response has been enabled.', ephemeral: true });
|
return interaction.editReply({ content: 'Auto response has been enabled.', ephemeral: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
client.on('interactionCreate', async (interactionMenu) => {
|
return listenButton(client, interaction, interaction.user);
|
||||||
if (interaction.user !== interactionMenu.user) return;
|
|
||||||
if (!interactionMenu.isButton) return;
|
|
||||||
interactionMenu.update({ components: [] });
|
|
||||||
if (interactionMenu.customId === `yes${interaction.user.id}`) {
|
|
||||||
const body = { serverID: interaction.guild.id, stat: 'disable' };
|
|
||||||
await db.autoresponseStat.update(body, { where: { serverID: interaction.guild.id } });
|
|
||||||
return interaction.editReply({ content: 'Auto response has been disabled.', ephemeral: true });
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function listenButton(client, interaction, user = interaction.user, originalId = interaction.id) {
|
||||||
|
client.once('interactionCreate', async (interactionMenu) => {
|
||||||
|
if (user !== interactionMenu.user) return listenButton(client, interaction, user, originalId);
|
||||||
|
if (!interactionMenu.isButton()) return;
|
||||||
|
|
||||||
|
await interactionMenu.update({ components: [] });
|
||||||
|
|
||||||
|
if (interactionMenu.customId === `yes${interaction.user.id}${originalId}`) {
|
||||||
|
const body = { serverID: interaction.guild.id, stat: 'disable' };
|
||||||
|
await db.autoresponseStat.update(body, { where: { serverID: interaction.guild.id } });
|
||||||
|
return interaction.editReply({ content: 'Auto response has been disabled.', ephemeral: true });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -25,44 +25,50 @@ export default {
|
||||||
const row = new ActionRowBuilder()
|
const row = new ActionRowBuilder()
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(`edit${interaction.user.id}`)
|
.setCustomId(`edit${interaction.user.id}${interaction.id}`)
|
||||||
.setLabel('Edit')
|
.setLabel('Edit')
|
||||||
.setStyle(ButtonStyle.Primary),
|
.setStyle(ButtonStyle.Primary),
|
||||||
)
|
)
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(`remove${interaction.user.id}`)
|
.setCustomId(`remove${interaction.user.id}${interaction.id}`)
|
||||||
.setLabel('Remove')
|
.setLabel('Remove')
|
||||||
.setStyle(ButtonStyle.Danger),
|
.setStyle(ButtonStyle.Danger),
|
||||||
)
|
)
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(`nothing${interaction.user.id}`)
|
.setCustomId(`nothing${interaction.user.id}${interaction.id}`)
|
||||||
.setLabel('Do nothing')
|
.setLabel('Do nothing')
|
||||||
.setStyle(ButtonStyle.Secondary),
|
.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 });
|
await interaction.reply({ content: 'The server already has a message set, do you want to edit it or remove it?', components: [row], ephemeral: true });
|
||||||
|
|
||||||
client.on('interactionCreate', async (interactionMenu) => {
|
return listenButton(client, interaction, args, interaction.user);
|
||||||
if (interaction.user !== interactionMenu.user) return;
|
|
||||||
if (!interactionMenu.isButton) return;
|
|
||||||
interactionMenu.update({ components: [] });
|
|
||||||
if (interactionMenu.customId === `edit${interaction.user.id}`) {
|
|
||||||
if (!args.message) {
|
|
||||||
return interaction.reply({ content: 'You need to input a message for me to edit!', ephemeral: true });
|
|
||||||
}
|
|
||||||
const body = { guildID: interaction.guild.id, channelID: interaction.channel.id, message: args.message };
|
|
||||||
await db.leaveChannel.update(body, { where: { guildID: interaction.guild.id } });
|
|
||||||
return interaction.editReply({ content: `The leave message has been set to ${args.message}`, ephemeral: true });
|
|
||||||
}
|
|
||||||
else if (interactionMenu.customId === `remove${interaction.user.id}`) {
|
|
||||||
db.leaveChannel.destroy({ where: { guildID: interaction.guild.id, channelID: interaction.channel.id } });
|
|
||||||
return interaction.editReply({ content: 'The leave message has been deleted.', ephemeral: true });
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function listenButton(client, interaction, args, user = interaction.user, originalId = interaction.id) {
|
||||||
|
client.once('interactionCreate', async (interactionMenu) => {
|
||||||
|
if (user !== interactionMenu.user) return listenButton(client, interaction, args, user, originalId);
|
||||||
|
if (!interactionMenu.isButton()) return;
|
||||||
|
|
||||||
|
await interactionMenu.update({ components: [] });
|
||||||
|
|
||||||
|
if (interactionMenu.customId === `edit${interaction.user.id}${originalId}`) {
|
||||||
|
if (!args.message) {
|
||||||
|
return interaction.reply({ content: 'You need to input a message for me to edit!', ephemeral: true });
|
||||||
|
}
|
||||||
|
const body = { guildID: interaction.guild.id, channelID: interaction.channel.id, message: args.message };
|
||||||
|
await db.leaveChannel.update(body, { where: { guildID: interaction.guild.id } });
|
||||||
|
return interaction.editReply({ content: `The leave message has been set to ${args.message}`, ephemeral: true });
|
||||||
|
}
|
||||||
|
else if (interactionMenu.customId === `remove${interaction.user.id}${originalId}`) {
|
||||||
|
db.leaveChannel.destroy({ where: { guildID: interaction.guild.id, channelID: interaction.channel.id } });
|
||||||
|
return interaction.editReply({ content: 'The leave message has been deleted.', ephemeral: true });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -20,13 +20,13 @@ export default {
|
||||||
const row = new ActionRowBuilder()
|
const row = new ActionRowBuilder()
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(`yes${interaction.user.id}`)
|
.setCustomId(`yes${interaction.user.id}${interaction.id}`)
|
||||||
.setLabel('Yes')
|
.setLabel('Yes')
|
||||||
.setStyle(ButtonStyle.Primary),
|
.setStyle(ButtonStyle.Primary),
|
||||||
)
|
)
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(`no${interaction.user.id}`)
|
.setCustomId(`no${interaction.user.id}${interaction.id}`)
|
||||||
.setLabel('No')
|
.setLabel('No')
|
||||||
.setStyle(ButtonStyle.Danger),
|
.setStyle(ButtonStyle.Danger),
|
||||||
);
|
);
|
||||||
|
@ -40,18 +40,23 @@ export default {
|
||||||
return interaction.editReply({ content: 'Quotation has been enabled.', ephemeral: true });
|
return interaction.editReply({ content: 'Quotation has been enabled.', ephemeral: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
client.on('interactionCreate', async (interactionMenu) => {
|
return listenButton(client, interaction, interaction.user);
|
||||||
if (interaction.user !== interactionMenu.user) return;
|
|
||||||
if (!interactionMenu.isButton) return;
|
|
||||||
interactionMenu.update({ components: [] });
|
|
||||||
if (interactionMenu.customId === `yes${interaction.user.id}`) {
|
|
||||||
const body = { serverID: interaction.guild.id, stat: 'disable' };
|
|
||||||
await db.quotationStat.update(body, { where: { serverID: interaction.guild.id } });
|
|
||||||
return interaction.editReply({ content: 'Quotation has been disabled.', ephemeral: true });
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function listenButton(client, interaction, user = interaction.user, originalId = interaction.id) {
|
||||||
|
client.once('interactionCreate', async (interactionMenu) => {
|
||||||
|
if (user !== interactionMenu.user) return listenButton(client, interaction, user, originalId);
|
||||||
|
if (!interactionMenu.isButton()) return;
|
||||||
|
|
||||||
|
interactionMenu.update({ components: [] });
|
||||||
|
|
||||||
|
if (interactionMenu.customId === `yes${interaction.user.id}${originalId}`) {
|
||||||
|
await db.quotationStat.destroy({ where: { serverID: interaction.guild.id } });
|
||||||
|
return interaction.editReply({ content: 'Quotation has been disabled.', ephemeral: true });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ export default {
|
||||||
.setName('shameboard')
|
.setName('shameboard')
|
||||||
.setDescription('Set shameboard to the current channel.')
|
.setDescription('Set shameboard to the current channel.')
|
||||||
.addStringOption(option =>
|
.addStringOption(option =>
|
||||||
option.setName('emote')
|
option.setName('emote')
|
||||||
.setDescription('The emote that should be used to enter the shameboard.'))
|
.setDescription('The emote that should be used to enter the shameboard.'))
|
||||||
.addStringOption(option =>
|
.addStringOption(option =>
|
||||||
option.setName('count')
|
option.setName('count')
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { SlashCommandBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder, PermissionFlagsBits } from 'discord.js';
|
import { SlashCommandBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder, PermissionFlagsBits, PermissionsBitField } from 'discord.js';
|
||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
|
|
||||||
|
@ -51,12 +51,12 @@ export default {
|
||||||
|
|
||||||
if (args.remove) {
|
if (args.remove) {
|
||||||
if (tag) {
|
if (tag) {
|
||||||
if (tag.get('ownerID') == interaction.user.id || interaction.member.permissionsIn(interaction.channel).has('ADMINISTRATOR') || interaction.user.id == ownerId) {
|
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 } });
|
db.Tag.destroy({ where: { trigger: args.trigger, serverID: interaction.guild.id } });
|
||||||
return interaction.editReply('successfully deleted the following tag: ' + args.trigger);
|
return interaction.editReply('successfully deleted the following tag: ' + args.trigger);
|
||||||
}
|
}
|
||||||
else {
|
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`);
|
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 {
|
else {
|
||||||
|
@ -77,56 +77,51 @@ export default {
|
||||||
const row = new ActionRowBuilder()
|
const row = new ActionRowBuilder()
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(`edit${interaction.user.id}`)
|
.setCustomId(`edit${interaction.user.id}${interaction.id}`)
|
||||||
.setLabel('Edit')
|
.setLabel('Edit')
|
||||||
.setStyle(ButtonStyle.Primary),
|
.setStyle(ButtonStyle.Primary),
|
||||||
)
|
)
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(`remove${interaction.user.id}`)
|
.setCustomId(`remove${interaction.user.id}${interaction.id}`)
|
||||||
.setLabel('Remove')
|
.setLabel('Remove')
|
||||||
.setStyle(ButtonStyle.Danger),
|
.setStyle(ButtonStyle.Danger),
|
||||||
)
|
)
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(`nothing${interaction.user.id}`)
|
.setCustomId(`nothing${interaction.user.id}${interaction.id}`)
|
||||||
.setLabel('Do nothing')
|
.setLabel('Do nothing')
|
||||||
.setStyle(ButtonStyle.Secondary),
|
.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 });
|
await interaction.editReply({ content: 'This tag already exist, do you want to update it, remove it or do nothing?', components: [row], ephemeral: true });
|
||||||
|
|
||||||
client.on('interactionCreate', async (interactionMenu) => {
|
return listenButton(client, interaction, args, interaction.user);
|
||||||
if (interaction.user !== interactionMenu.user) return;
|
|
||||||
if (!interactionMenu.isButton) return;
|
|
||||||
interactionMenu.update({ components: [] });
|
|
||||||
if (interactionMenu.customId === `edit${interaction.user.id}`) {
|
|
||||||
const body = { trigger: args.trigger, response: args.response, ownerID: interaction.user.id, serverID: interaction.guild.id };
|
|
||||||
await db.joinChannel.update(body, { where: { guildID: interaction.guild.id } });
|
|
||||||
return interaction.editReply({ content: `The tag ${args.trigger} has been set to ${args.response}`, ephemeral: true });
|
|
||||||
}
|
|
||||||
else if (interactionMenu.customId === `remove${interaction.user.id}`) {
|
|
||||||
db.Tag.destroy({ where: { trigger: args.trigger, serverID: interaction.guild.id } });
|
|
||||||
return interaction.editReply({ content: `The tag ${args.trigger} has been deleted`, ephemeral: true });
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
else {
|
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`);
|
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`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const join = await db.joinChannel.findOne({ where: { guildID: interaction.guild.id } });
|
|
||||||
|
|
||||||
if (!join && !args.message) {
|
|
||||||
return interaction.editReply({ 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.editReply({ content: `The join message have been set with ${args.message}`, ephemeral: true });
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function listenButton(client, interaction, args, user = interaction.user, originalId = interaction.id) {
|
||||||
|
client.once('interactionCreate', async (interactionMenu) => {
|
||||||
|
if (user !== interactionMenu.user) return listenButton(client, interaction, args, user, originalId);
|
||||||
|
if (!interactionMenu.isButton()) return;
|
||||||
|
|
||||||
|
await interactionMenu.update({ components: [] });
|
||||||
|
|
||||||
|
if (interactionMenu.customId === `edit${interaction.user.id}${originalId}`) {
|
||||||
|
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 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -26,44 +26,50 @@ export default {
|
||||||
const row = new ActionRowBuilder()
|
const row = new ActionRowBuilder()
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(`edit${interaction.user.id}`)
|
.setCustomId(`edit${interaction.user.id}${interaction.id}`)
|
||||||
.setLabel('Edit')
|
.setLabel('Edit')
|
||||||
.setStyle(ButtonStyle.Primary),
|
.setStyle(ButtonStyle.Primary),
|
||||||
)
|
)
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(`remove${interaction.user.id}`)
|
.setCustomId(`remove${interaction.user.id}${interaction.id}`)
|
||||||
.setLabel('Remove')
|
.setLabel('Remove')
|
||||||
.setStyle(ButtonStyle.Danger),
|
.setStyle(ButtonStyle.Danger),
|
||||||
)
|
)
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(`nothing${interaction.user.id}`)
|
.setCustomId(`nothing${interaction.user.id}${interaction.id}`)
|
||||||
.setLabel('Do nothing')
|
.setLabel('Do nothing')
|
||||||
.setStyle(ButtonStyle.Secondary),
|
.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 });
|
await interaction.reply({ content: 'The server already has a message set, do you want to edit it or remove it?', components: [row], ephemeral: true });
|
||||||
|
|
||||||
client.on('interactionCreate', async (interactionMenu) => {
|
return listenButton(client, interaction, args, interaction.user);
|
||||||
if (interaction.user !== interactionMenu.user) return;
|
|
||||||
if (!interactionMenu.isButton) return;
|
|
||||||
interactionMenu.update({ components: [] });
|
|
||||||
if (interactionMenu.customId === `edit${interaction.user.id}`) {
|
|
||||||
if (!args.message) {
|
|
||||||
return interaction.reply({ content: 'You need to input a message for me to edit!', ephemeral: true });
|
|
||||||
}
|
|
||||||
const body = { guildID: interaction.guild.id, channelID: interaction.channel.id, message: args.message };
|
|
||||||
await db.joinChannel.update(body, { where: { guildID: interaction.guild.id } });
|
|
||||||
return interaction.editReply({ content: `The join message has been set to ${args.message}`, ephemeral: true });
|
|
||||||
}
|
|
||||||
else if (interactionMenu.customId === `remove${interaction.user.id}`) {
|
|
||||||
db.joinChannel.destroy({ where: { guildID: interaction.guild.id, channelID: interaction.channel.id } });
|
|
||||||
return interaction.editReply({ content: 'The join message has been deleted.', ephemeral: true });
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function listenButton(client, interaction, args, user = interaction.user, originalId = interaction.id) {
|
||||||
|
client.once('interactionCreate', async (interactionMenu) => {
|
||||||
|
if (user !== interactionMenu.user) return listenButton(client, interaction, args, user, originalId);
|
||||||
|
if (!interactionMenu.isButton()) return;
|
||||||
|
|
||||||
|
await interactionMenu.update({ components: [] });
|
||||||
|
|
||||||
|
if (interactionMenu.customId === `edit${interaction.user.id}${originalId}`) {
|
||||||
|
if (!args.message) {
|
||||||
|
return interaction.reply({ content: 'You need to input a message for me to edit!', ephemeral: true });
|
||||||
|
}
|
||||||
|
const body = { guildID: interaction.guild.id, channelID: interaction.channel.id, message: args.message };
|
||||||
|
await db.joinChannel.update(body, { where: { guildID: interaction.guild.id } });
|
||||||
|
return interaction.editReply({ content: `The join message has been set to ${args.message}`, ephemeral: true });
|
||||||
|
}
|
||||||
|
else if (interactionMenu.customId === `remove${interaction.user.id}${originalId}`) {
|
||||||
|
db.joinChannel.destroy({ where: { guildID: interaction.guild.id, channelID: interaction.channel.id } });
|
||||||
|
return interaction.editReply({ content: 'The join message has been deleted.', ephemeral: true });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ 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' assert {type: 'json'};
|
import fourChan from '../../json/4chan.json' with {type: 'json'};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
|
|
60
commands/fun/audio2image.js
Normal file
60
commands/fun/audio2image.js
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
59
commands/fun/image2audio.js
Normal file
59
commands/fun/image2audio.js
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
/* TODO
|
||||||
|
*
|
||||||
|
* Merge with commands/fun/audio2image.js
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import os from 'node:os';
|
||||||
|
import fetch from 'node-fetch';
|
||||||
|
import util from 'node:util';
|
||||||
|
import stream from 'node:stream';
|
||||||
|
import utils from '../../utils/videos.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('image2audio')
|
||||||
|
.setDescription('Transform an image binary data into audio ( MIGHT BE VERY LOUD )')
|
||||||
|
.addAttachmentOption(option =>
|
||||||
|
option.setName('img')
|
||||||
|
.setDescription('The image that will become audio. Only tested with png and jpg.')
|
||||||
|
.setRequired(true)),
|
||||||
|
category: 'fun',
|
||||||
|
alias: ['i2a'],
|
||||||
|
async execute(interaction, args) {
|
||||||
|
if (!args.img) return interaction.reply('Please attach an image with your message.');
|
||||||
|
|
||||||
|
await interaction.deferReply();
|
||||||
|
|
||||||
|
ifExistDelete(`${os.tmpdir()}/${args.img.name}`);
|
||||||
|
ifExistDelete(`${os.tmpdir()}/1${args.img.name}`);
|
||||||
|
ifExistDelete(`${os.tmpdir()}/${args.img.name}.mp3`);
|
||||||
|
|
||||||
|
const streamPipeline = util.promisify(stream.pipeline);
|
||||||
|
const res = await fetch(args.img.url);
|
||||||
|
if (!res.ok) return interaction.editReply('An error has occured while trying to download your image.');
|
||||||
|
await streamPipeline(res.body, fs.createWriteStream(`${os.tmpdir()}/${args.img.name}`));
|
||||||
|
|
||||||
|
await utils.ffmpeg(['-i', `${os.tmpdir()}/${args.img.name}`, '-f', 'rawvideo', `${os.tmpdir()}/1${args.img.name}`]);
|
||||||
|
await utils.ffmpeg(['-sample_rate', '44100', '-ac', '1', '-f', 's16le', '-i', `${os.tmpdir()}/1${args.img.name}`, `${os.tmpdir()}/${args.img.name}.mp3`]);
|
||||||
|
|
||||||
|
const file = fs.statSync(`${os.tmpdir()}/${args.img.name}.mp3`);
|
||||||
|
const fileSize = (file.size / 1000000.0).toFixed(2);
|
||||||
|
|
||||||
|
if (fileSize > await utils.getMaxFileSize(interaction.guild)) return interaction.editReply('error');
|
||||||
|
interaction.editReply({ content: `Audio file is ${fileSize} MB` });
|
||||||
|
return interaction.followUp({ files: [`${os.tmpdir()}/${args.img.name}.mp3`] });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async function ifExistDelete(path) {
|
||||||
|
if (fs.existsSync(path)) {
|
||||||
|
fs.rm(path, (err) => {
|
||||||
|
console.log('deleted');
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import { SlashCommandBuilder } from 'discord.js';
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
import { EmbedBuilder } from 'discord.js';
|
import { EmbedBuilder } from 'discord.js';
|
||||||
import Twit from 'twit';
|
import { TwitterApi } from 'twitter-api-v2';
|
||||||
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';
|
||||||
|
@ -8,7 +8,7 @@ import util from 'node:util';
|
||||||
import stream from 'node:stream';
|
import stream from 'node:stream';
|
||||||
|
|
||||||
import db from '../../models/index.js';
|
import db from '../../models/index.js';
|
||||||
import wordToCensor from '../../json/censor.json' assert {type: 'json'};
|
import wordToCensor from '../../json/censor.json' with {type: 'json'};
|
||||||
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,10 +16,10 @@ const Blacklists = db.Blacklists;
|
||||||
export default {
|
export default {
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
.setName('tweet')
|
.setName('tweet')
|
||||||
.setDescription('Send tweet from Haha yes twitter account. Please do not use it for advertisement and keep it english')
|
.setDescription('Send tweet from the bot twitter account. Please do not use it for advertisement and keep it english')
|
||||||
.addStringOption(option =>
|
.addStringOption(option =>
|
||||||
option.setName('content')
|
option.setName('content')
|
||||||
.setDescription('The content of the tweet you want to send me.')
|
.setDescription('!THIS IS NOT FEEDBACK! The content of the tweet you want to send me.')
|
||||||
.setRequired(false))
|
.setRequired(false))
|
||||||
.addAttachmentOption(option =>
|
.addAttachmentOption(option =>
|
||||||
option.setName('image')
|
option.setName('image')
|
||||||
|
@ -28,6 +28,7 @@ export default {
|
||||||
category: 'fun',
|
category: 'fun',
|
||||||
ratelimit: 3,
|
ratelimit: 3,
|
||||||
cooldown: 86400,
|
cooldown: 86400,
|
||||||
|
guildOnly: true,
|
||||||
async execute(interaction, args, client) {
|
async execute(interaction, args, client) {
|
||||||
const content = args.content;
|
const content = args.content;
|
||||||
const attachment = args.image;
|
const attachment = args.image;
|
||||||
|
@ -39,13 +40,31 @@ export default {
|
||||||
await interaction.deferReply({ ephemeral: false });
|
await interaction.deferReply({ ephemeral: false });
|
||||||
let tweet = content;
|
let tweet = content;
|
||||||
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 current date so it checks correctly for the 1 year requirement.
|
// Reset the date for the next check
|
||||||
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
|
||||||
|
@ -54,13 +73,25 @@ export default {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove zero width space
|
|
||||||
if (tweet) {
|
if (tweet) {
|
||||||
|
// remove zero width space
|
||||||
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 });
|
||||||
|
}
|
||||||
|
|
||||||
if (tweet) {
|
wordToCensor.forEach(async word => {
|
||||||
|
if (tweet.toLowerCase().includes(word.toLowerCase())) {
|
||||||
|
const body = { type:'tweet', uid: interaction.user.id, reason: 'Automatic ban from banned word.' };
|
||||||
|
Blacklists.create(body);
|
||||||
|
|
||||||
|
await interaction.editReply({ content: 'Sike, you just posted cringe! Enjoy the blacklist :)' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
// 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.substring(0, tweet.length - 1)) || wordToCensor.includes(tweet.substring(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);
|
||||||
|
@ -68,6 +99,7 @@ export default {
|
||||||
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')) {
|
||||||
|
@ -81,11 +113,12 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const T = new Twit({
|
|
||||||
consumer_key: twiConsumer,
|
const userClient = new TwitterApi({
|
||||||
consumer_secret: twiConsumerSecret,
|
appKey: twiConsumer,
|
||||||
access_token: twiToken,
|
appSecret: twiConsumerSecret,
|
||||||
access_token_secret: twiTokenSecret,
|
accessToken: twiToken,
|
||||||
|
accessSecret: twiTokenSecret,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -107,17 +140,8 @@ 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 b64Image = fs.readFileSync(`${os.tmpdir()}/${attachment.name}`, { encoding: 'base64' });
|
const image = await userClient.v1.uploadMedia(`${os.tmpdir()}/${attachment.name}`);
|
||||||
T.post('media/upload', { media_data: b64Image }, function(err, data) {
|
Tweet(image);
|
||||||
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' });
|
||||||
|
@ -134,74 +158,47 @@ export default {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Tweet(data) {
|
async function Tweet(img) {
|
||||||
let options = {
|
let options = null;
|
||||||
status: tweet,
|
if (img) {
|
||||||
};
|
options = { media: { media_ids: new Array(img) } };
|
||||||
|
|
||||||
if (data && tweet) {
|
|
||||||
options = {
|
|
||||||
status: tweet,
|
|
||||||
media_ids: new Array(data.media_id_string),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
else if (data) {
|
const tweeted = await userClient.v2.tweet(tweet, options);
|
||||||
options = {
|
|
||||||
media_ids: new Array(data.media_id_string),
|
const tweetid = tweeted.data.id;
|
||||||
};
|
const FunnyWords = ['oppaGangnamStyle', '69', '420', 'cum', 'funnyMan', 'GUCCISmartToilet', 'TwitterForClowns', 'fart', 'ok', 'hi', 'howAreYou', 'WhatsNinePlusTen', '21'];
|
||||||
|
const TweetLink = `https://vxtwitter.com/${FunnyWords[Math.floor((Math.random() * FunnyWords.length))]}/status/${tweetid}`;
|
||||||
|
|
||||||
|
let channel = await client.channels.resolve(twiChannel);
|
||||||
|
channel.send(TweetLink);
|
||||||
|
|
||||||
|
const Embed = new EmbedBuilder()
|
||||||
|
.setAuthor({ name: interaction.user.username, iconURL: interaction.user.displayAvatarURL() })
|
||||||
|
.setDescription(tweet ? tweet : 'No content.')
|
||||||
|
.addFields(
|
||||||
|
{ name: 'Link', value: TweetLink, inline: true },
|
||||||
|
{ name: 'Tweet ID', value: tweetid, inline: true },
|
||||||
|
{ name: 'Channel ID', value: interaction.channel.id, inline: true },
|
||||||
|
{ name: 'Message ID', value: interaction.id, inline: true },
|
||||||
|
{ name: 'Author', value: `${interaction.user.username} (${interaction.user.id})`, inline: true },
|
||||||
|
)
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
if (interaction.guild) {
|
||||||
|
Embed.addFields(
|
||||||
|
{ name: 'Guild', value: `${interaction.guild.name} (${interaction.guild.id})`, inline: true },
|
||||||
|
{ name: 'message link', value: `https://discord.com/channels/${interaction.guild.id}/${interaction.channel.id}/${interaction.id}`, inline: true },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Embed.addFields({ name: 'message link', value: `https://discord.com/channels/@me/${interaction.channel.id}/${interaction.id}` });
|
||||||
}
|
}
|
||||||
|
|
||||||
T.post('statuses/update', options, function(err, response) {
|
if (attachment) Embed.setImage(attachment.url);
|
||||||
if (err) {
|
|
||||||
// Rate limit exceeded
|
|
||||||
if (err.code == 88) return interaction.editReply({ content: err.interaction });
|
|
||||||
// Tweet needs to be a bit shorter.
|
|
||||||
if (err.code == 186) return interaction.editReply({ content: `${err.interaction} Your interaction was ${tweet.length} characters, you need to remove ${tweet.length - 280} characters (This count may be inaccurate if your interaction contained link)` });
|
|
||||||
// Status is a duplicate.
|
|
||||||
if (err.code == 187) return interaction.editReply({ content: err.interaction });
|
|
||||||
// To protect our users from spam and other malicious activity, this account is temporarily locked.
|
|
||||||
if (err.code == 326) return interaction.editReply({ content: err.interaction });
|
|
||||||
console.error('OH NO!!!!');
|
|
||||||
console.error(err);
|
|
||||||
return interaction.editReply({ content: 'OH NO!!! AN ERROR HAS occurred!!! please hold on while i find what\'s causing this issue!' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const tweetid = response.id_str;
|
channel = await client.channels.resolve(twiLogChannel);
|
||||||
const FunnyWords = ['oppaGangnamStyle', '69', '420', 'cum', 'funnyMan', 'GUCCISmartToilet', 'TwitterForClowns', 'fart', 'ok', 'hi', 'howAreYou', 'WhatsNinePlusTen', '21'];
|
channel.send({ embeds: [Embed] });
|
||||||
const TweetLink = `https://twitter.com/${FunnyWords[Math.floor((Math.random() * FunnyWords.length))]}/status/${tweetid}`;
|
return interaction.editReply({ content: `Go see ur epic tweet ${TweetLink}` });
|
||||||
|
|
||||||
// Im too lazy for now to make an entry in config.json
|
|
||||||
let channel = client.channels.resolve(twiChannel);
|
|
||||||
channel.send(TweetLink);
|
|
||||||
|
|
||||||
const Embed = new EmbedBuilder()
|
|
||||||
.setAuthor({ name: interaction.user.username, iconURL: interaction.user.displayAvatarURL() })
|
|
||||||
.setDescription(tweet ? tweet : 'No content.')
|
|
||||||
.addFields(
|
|
||||||
{ name: 'Link', value: TweetLink, inline: true },
|
|
||||||
{ name: 'Tweet ID', value: tweetid, inline: true },
|
|
||||||
{ name: 'Channel ID', value: interaction.channel.id, inline: true },
|
|
||||||
{ name: 'Message ID', value: interaction.id, inline: true },
|
|
||||||
{ name: 'Author', value: `${interaction.user.username} (${interaction.user.id})`, inline: true },
|
|
||||||
)
|
|
||||||
.setTimestamp();
|
|
||||||
|
|
||||||
if (interaction.guild) {
|
|
||||||
Embed.addFields(
|
|
||||||
{ name: 'Guild', value: `${interaction.guild.name} (${interaction.guild.id})`, inline: true },
|
|
||||||
{ name: 'message link', value: `https://discord.com/channels/${interaction.guild.id}/${interaction.channel.id}/${interaction.id}`, inline: true },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Embed.addFields({ name: 'message link', value: `https://discord.com/channels/@me/${interaction.channel.id}/${interaction.id}` });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attachment) Embed.setImage(attachment.url);
|
|
||||||
|
|
||||||
channel = client.channels.resolve(twiLogChannel);
|
|
||||||
channel.send({ embeds: [Embed] });
|
|
||||||
return interaction.editReply({ content: `Go see ur epic tweet ${TweetLink}` });
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,6 +12,9 @@ export default {
|
||||||
.setDescription('Force the generation of the video in non-nsfw channel.')
|
.setDescription('Force the generation of the video in non-nsfw channel.')
|
||||||
.setRequired(false)),
|
.setRequired(false)),
|
||||||
category: 'fun',
|
category: 'fun',
|
||||||
|
ratelimit: 2,
|
||||||
|
cooldown: 60,
|
||||||
|
parallelLimit: 30,
|
||||||
async execute(interaction, args) {
|
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`);
|
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`);
|
||||||
|
|
||||||
|
@ -69,19 +72,19 @@ export default {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
new YTPGenerator().configurateAndGo(options)
|
await new YTPGenerator().configurateAndGo(options)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
loadingmsg.delete();
|
loadingmsg.delete();
|
||||||
return interaction.reply({ content: 'Here is your YTP! Remember, it might contain nsfw, so be careful!', files: [`${os.tmpdir()}/${interaction.id}_YTP.mp4`] })
|
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 => {
|
.catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
return interaction.reply('Whoops, look like the vid might be too big for discord, my bad, please try again');
|
return interaction.followUp({ files: [`./asset/ytp/error${Math.floor(Math.random() * 2) + 1}.mp4`] });
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
loadingmsg.delete();
|
loadingmsg.delete();
|
||||||
return interaction.reply({ files: [`./asset/ytp/error${Math.floor(Math.random() * 2) + 1}.mp4`] });
|
return interaction.followUp({ files: [`./asset/ytp/error${Math.floor(Math.random() * 2) + 1}.mp4`] });
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// TODO
|
||||||
|
// Switch to 'twitter-api-v2'
|
||||||
import { SlashCommandBuilder } from 'discord.js';
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
import Twit from 'twit';
|
import Twit from 'twit';
|
||||||
|
|
|
@ -43,7 +43,7 @@ export default {
|
||||||
.setTimestamp();
|
.setTimestamp();
|
||||||
|
|
||||||
user.send({ embeds: [Embed] });
|
user.send({ embeds: [Embed] });
|
||||||
return interaction.reply({ content: `DM sent to ${user.username}`, ephemeral: true });
|
return interaction.reply({ content: `DM sent to ${user.username} (${user.id})` });
|
||||||
/*
|
/*
|
||||||
const Attachment = (message.attachments).array();
|
const Attachment = (message.attachments).array();
|
||||||
if (Attachment[0]) {
|
if (Attachment[0]) {
|
||||||
|
@ -58,10 +58,10 @@ export default {
|
||||||
else {
|
else {
|
||||||
client.users.resolve(user).send(Embed)
|
client.users.resolve(user).send(Embed)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return interaction.reply(`DM sent to ${user.tag}`);
|
return interaction.reply(`DM sent to ${user.username}`);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
return interaction.reply(`Could not send a DM to ${user.tag}`);
|
return interaction.reply(`Could not send a DM to ${user.username}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
30
commands/owner/download&load.js
Normal file
30
commands/owner/download&load.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
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.`);
|
||||||
|
},
|
||||||
|
};
|
|
@ -32,42 +32,58 @@ export default {
|
||||||
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);
|
||||||
let user = userid;
|
if (command === 'guild') {
|
||||||
await client.users.fetch(userid);
|
const guildid = userid;
|
||||||
user = client.users.resolve(userid).tag;
|
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;
|
||||||
|
await client.users.fetch(userid);
|
||||||
|
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 ActionRowBuilder()
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(`yes${interaction.user.id}`)
|
.setCustomId(`yes${interaction.user.id}${interaction.id}`)
|
||||||
.setLabel('Yes')
|
.setLabel('Yes')
|
||||||
.setStyle(ButtonStyle.Primary),
|
.setStyle(ButtonStyle.Primary),
|
||||||
)
|
)
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(`no${interaction.user.id}`)
|
.setCustomId(`no${interaction.user.id}${interaction.id}`)
|
||||||
.setLabel('No')
|
.setLabel('No')
|
||||||
.setStyle(ButtonStyle.Danger),
|
.setStyle(ButtonStyle.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] });
|
||||||
|
|
||||||
interaction.client.once('interactionCreate', async (interactionMenu) => {
|
return listenButton(client, interaction, command, userid, interaction.user);
|
||||||
if (interaction.user !== interactionMenu.user) return;
|
|
||||||
if (!interactionMenu.isButton) return;
|
|
||||||
interactionMenu.update({ components: [] });
|
|
||||||
if (interactionMenu.customId === `yes${interaction.user.id}`) {
|
|
||||||
Blacklists.destroy({ where: { type:command, uid:userid } });
|
|
||||||
return interaction.editReply(`The following ID have been unblacklisted from ${command}: ${userid}`);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return interaction.editReply('No one has been unblacklisted.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function listenButton(client, interaction, command, userid, user = interaction.user, originalId = interaction.id) {
|
||||||
|
client.once('interactionCreate', async (interactionMenu) => {
|
||||||
|
if (user !== interactionMenu.user) return listenButton(client, interaction, command, userid, user, originalId);
|
||||||
|
if (!interactionMenu.isButton()) return;
|
||||||
|
|
||||||
|
await interactionMenu.update({ components: [] });
|
||||||
|
|
||||||
|
if (interactionMenu.customId === `yes${interaction.user.id}${originalId}`) {
|
||||||
|
Blacklists.destroy({ where: { type:command, uid:userid } });
|
||||||
|
return interaction.editReply(`The following ID have been unblacklisted from ${command}: ${userid}`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return interaction.editReply('No one has been unblacklisted.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -30,7 +30,7 @@ export default {
|
||||||
data: ${JSON.stringify(client.commands.get(args.commandname).data)},
|
data: ${JSON.stringify(client.commands.get(args.commandname).data)},
|
||||||
category: '${client.commands.get(args.commandname).category}',
|
category: '${client.commands.get(args.commandname).category}',
|
||||||
async execute(interaction) {
|
async execute(interaction) {
|
||||||
return interaction.reply('${args.placeholder}');
|
return interaction.reply('${args.placeholder.replace(/'/g, '\\\'')}');
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { SlashCommandBuilder } from 'discord.js';
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
import { EmbedBuilder } from 'discord.js';
|
import { EmbedBuilder } from 'discord.js';
|
||||||
import { exec } from 'node:child_process';
|
import { execFile } from 'node:child_process';
|
||||||
import db from '../../models/index.js';
|
import db from '../../models/index.js';
|
||||||
const donator = db.donator;
|
const donator = db.donator;
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ export default {
|
||||||
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 owner = await client.users.fetch('267065637183029248');
|
const creator = 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 = 'I\'m a fun multipurpose bot made using [discord.js](https://github.com/discordjs/discord.js)'
|
||||||
|
@ -25,7 +25,7 @@ export default {
|
||||||
for (let i = 0; i < Donator.length; i++) {
|
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.tag} (${user.id}) | ${Donator[i].get('comment')}**\n`;
|
description += `**${user.username} (${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,26 @@ export default {
|
||||||
description += 'No one :(\n';
|
description += 'No one :(\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
description += `\nThanks to ${tina.tag} (336492042299637771) for inspiring me for making this bot!`;
|
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!';
|
||||||
|
|
||||||
exec('git rev-parse --short HEAD', (err, stdout) => {
|
execFile('git', ['rev-parse', '--short', 'HEAD'], (err, stdout) => {
|
||||||
const aboutEmbed = new EmbedBuilder()
|
const aboutEmbed = new EmbedBuilder()
|
||||||
.setColor(interaction.member ? interaction.member.displayHexColor : 'Navy')
|
.setColor(interaction.member ? interaction.member.displayHexColor : 'Navy')
|
||||||
.setAuthor({ name: client.user.tag, iconURL: client.user.displayAvatarURL(), url: 'https://libtar.de' })
|
.setAuthor({ name: client.user.username, iconURL: client.user.displayAvatarURL(), url: 'https://libtar.de' })
|
||||||
.setTitle('About me')
|
.setTitle('About me')
|
||||||
.setDescription(description)
|
.setDescription(description)
|
||||||
.addFields(
|
.addFields(
|
||||||
{ name: 'Current commit', value: stdout },
|
{ name: 'Current commit', value: stdout },
|
||||||
{ name: 'Current maintainer', value: `${maintainer.tag} (${ownerId})` },
|
{ name: 'Current maintainer', value: `${maintainer.username} (${ownerId})` },
|
||||||
{ name: 'Gitea (Main)', value: 'https://git.namejeff.xyz/Supositware/Haha-Yes', inline: true },
|
{ name: 'Gitea (Main)', value: 'https://git.namejeff.xyz/Supositware/Haha-Yes', inline: true },
|
||||||
{ name: 'Github (Mirror)', value: 'https://github.com/Supositware/Haha-yes', inline: true },
|
{ name: 'Github (Mirror)', value: 'https://github.com/Supositware/Haha-yes', inline: true },
|
||||||
{ name: 'Privacy Policy', value: 'https://libtar.de/discordprivacy.txt', inline: true },
|
{ name: 'Privacy Policy', value: 'https://libtar.de/discordprivacy.txt', inline: true },
|
||||||
{ name: 'Status page', value: uptimePage.toString(), inline: true },
|
{ name: 'Status page', value: uptimePage.toString(), inline: true },
|
||||||
|
|
||||||
)
|
)
|
||||||
.setFooter({ text: `Original bot made by ${owner.tag} (267065637183029248)` });
|
.setFooter({ text: `Original bot made by ${creator.username} (267065637183029248)` });
|
||||||
|
|
||||||
interaction.reply({ embeds: [aboutEmbed] });
|
interaction.reply({ embeds: [aboutEmbed] });
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,6 +18,11 @@ export default {
|
||||||
cooldown: 86400,
|
cooldown: 86400,
|
||||||
async execute(interaction, args) {
|
async execute(interaction, args) {
|
||||||
const url = args.url;
|
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)) {
|
if (!await utils.stringIsAValidurl(url)) {
|
||||||
console.error(`Not a url!!! ${url}`);
|
console.error(`Not a url!!! ${url}`);
|
||||||
return interaction.reply({ content: '❌ This does not look like a valid url!', ephemeral: true });
|
return interaction.reply({ content: '❌ This does not look like a valid url!', ephemeral: true });
|
||||||
|
@ -25,7 +30,7 @@ export default {
|
||||||
|
|
||||||
await interaction.deferReply({ ephemeral: true });
|
await interaction.deferReply({ ephemeral: true });
|
||||||
|
|
||||||
utils.downloadVideo(url, interaction.id, 'mp4')
|
utils.downloadVideo(url, interaction.id, 'bestvideo[height<=?480]+bestaudio/best')
|
||||||
.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));
|
||||||
const output = `${os.tmpdir()}/${file}`;
|
const output = `${os.tmpdir()}/${file}`;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { SlashCommandBuilder } from 'discord.js';
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
import { EmbedBuilder } from 'discord.js';
|
import { EmbedBuilder } from 'discord.js';
|
||||||
import donations from '../../json/donations.json' assert {type: 'json'};
|
import donations from '../../json/donations.json' with {type: 'json'};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
|
|
|
@ -17,7 +17,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.tag} (${user.id}) | ${Donator[i].get('comment')}**\n`;}
|
if (user !== null) {donatorMessage += `**${user.username} (${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`;}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
import { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, SelectMenuBuilder } from 'discord.js';
|
import { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, StringSelectMenuBuilder } from 'discord.js';
|
||||||
import { exec } from 'node:child_process';
|
import { execFile } 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 client;
|
||||||
let cleanUp;
|
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()
|
||||||
|
@ -21,19 +26,27 @@ 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',
|
category: 'utility',
|
||||||
alias: ['dl'],
|
alias: ['dl'],
|
||||||
|
integration_types: [0, 1],
|
||||||
|
|
||||||
async execute(interaction, args, c) {
|
async execute(interaction, args, c) {
|
||||||
client = c;
|
client = c;
|
||||||
const url = args.url;
|
const url = args.url;
|
||||||
const format = args.format;
|
const format = args.format;
|
||||||
|
maxFileSize = await utils.getMaxFileSize(interaction.guild);
|
||||||
interaction.doCompress = args.compress;
|
interaction.doCompress = args.compress;
|
||||||
if (interaction.cleanUp) {
|
interaction.doAutocrop = args.autocrop;
|
||||||
cleanUp = interaction.cleanUp;
|
|
||||||
}
|
|
||||||
|
|
||||||
await interaction.deferReply({ ephemeral: false });
|
await interaction.deferReply({ ephemeral: false });
|
||||||
|
|
||||||
|
@ -48,7 +61,12 @@ export default {
|
||||||
|
|
||||||
if (format) {
|
if (format) {
|
||||||
let qualitys = await new Promise((resolve, reject) => {
|
let qualitys = await new Promise((resolve, reject) => {
|
||||||
exec(`./bin/yt-dlp "${url}" --print "%()j"`, (err, stdout, stderr) => {
|
const options = [url, '--print', '%()j'];
|
||||||
|
if (proxy) {
|
||||||
|
options.push('--proxy');
|
||||||
|
options.push(proxy);
|
||||||
|
};
|
||||||
|
execFile('./bin/yt-dlp', options, (err, stdout, stderr) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(stderr);
|
reject(stderr);
|
||||||
}
|
}
|
||||||
|
@ -58,8 +76,8 @@ 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 => {
|
||||||
|
@ -89,8 +107,8 @@ export default {
|
||||||
|
|
||||||
const row = new ActionRowBuilder()
|
const row = new ActionRowBuilder()
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new SelectMenuBuilder()
|
new StringSelectMenuBuilder()
|
||||||
.setCustomId(`downloadQuality${interaction.user.id}`)
|
.setCustomId(`downloadQuality${interaction.user.id}${interaction.id}`)
|
||||||
.setPlaceholder('Nothing selected')
|
.setPlaceholder('Nothing selected')
|
||||||
.setMinValues(1)
|
.setMinValues(1)
|
||||||
.setMaxValues(2)
|
.setMaxValues(2)
|
||||||
|
@ -103,25 +121,37 @@ export default {
|
||||||
client.on('interactionCreate', async (interactionMenu) => {
|
client.on('interactionCreate', async (interactionMenu) => {
|
||||||
if (interaction.user !== interactionMenu.user) return;
|
if (interaction.user !== interactionMenu.user) return;
|
||||||
if (!interactionMenu.isSelectMenu()) return;
|
if (!interactionMenu.isSelectMenu()) return;
|
||||||
if (interactionMenu.customId === `downloadQuality${interaction.user.id}`) {
|
if (interactionMenu.customId === `downloadQuality${interaction.user.id}${interaction.id}`) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
download(url, interaction);
|
const newFormat = await checkSize(url, undefined, args, interaction);
|
||||||
|
return download(url, interaction, interaction, newFormat, args.description);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
async function download(url, interaction, originalInteraction) {
|
async function download(url, interaction, originalInteraction, format = undefined, description = false) {
|
||||||
let format = 'bestvideo*+bestaudio/best';
|
let embedColour = 'Navy';
|
||||||
|
if (interaction.member) {
|
||||||
|
if (interaction.member.displayHexColor) {
|
||||||
|
embedColour = interaction.member.displayHexColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
const Embed = new EmbedBuilder()
|
const Embed = new EmbedBuilder()
|
||||||
.setColor(interaction.member ? interaction.member.displayHexColor : 'Navy')
|
.setColor(embedColour)
|
||||||
.setAuthor({ name: `Downloaded by ${interaction.user.tag}`, iconURL: interaction.user.displayAvatarURL(), url: url })
|
.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.tag}" message!` });
|
.setFooter({ text: `You can get the original video by clicking on the "Downloaded by ${interaction.user.username}" message!` });
|
||||||
|
|
||||||
if (interaction.customId === `downloadQuality${interaction.user.id}`) {
|
if (description) {
|
||||||
|
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];
|
||||||
}
|
}
|
||||||
|
@ -131,11 +161,9 @@ async function download(url, interaction, originalInteraction) {
|
||||||
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}`;
|
let 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.doCompress) {
|
||||||
const presets = [ 'Social 8 MB 3 Minutes 360p30', 'Social 50 MB 10 Minutes 480p30', 'Social 50 MB 5 Minutes 720p30', 'Social 100 MB 5 Minutes 1080p30' ];
|
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 options = [];
|
const options = [];
|
||||||
|
|
||||||
presets.forEach(p => {
|
presets.forEach(p => {
|
||||||
|
@ -147,8 +175,8 @@ async function download(url, interaction, originalInteraction) {
|
||||||
|
|
||||||
const row = new ActionRowBuilder()
|
const row = new ActionRowBuilder()
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new SelectMenuBuilder()
|
new StringSelectMenuBuilder()
|
||||||
.setCustomId(`preset${interaction.user.id}`)
|
.setCustomId(`preset${interaction.user.id}${interaction.id}`)
|
||||||
.setPlaceholder('Nothing selected')
|
.setPlaceholder('Nothing selected')
|
||||||
.addOptions(options),
|
.addOptions(options),
|
||||||
);
|
);
|
||||||
|
@ -158,42 +186,75 @@ async function download(url, interaction, originalInteraction) {
|
||||||
client.on('interactionCreate', async (interactionMenu) => {
|
client.on('interactionCreate', async (interactionMenu) => {
|
||||||
if (interaction.user !== interactionMenu.user) return;
|
if (interaction.user !== interactionMenu.user) return;
|
||||||
if (!interactionMenu.isSelectMenu()) return;
|
if (!interactionMenu.isSelectMenu()) return;
|
||||||
if (interactionMenu.customId === `preset${interaction.user.id}`) {
|
if (interactionMenu.customId === `preset${interaction.user.id}${interaction.id}`) {
|
||||||
await interactionMenu.deferReply({ ephemeral: false });
|
await interactionMenu.deferReply({ ephemeral: false });
|
||||||
compress(file, interactionMenu, Embed);
|
compress(file, interactionMenu, Embed);
|
||||||
if (interaction.isMessage) cleanUp();
|
if (interaction.isMessage) {
|
||||||
|
interaction.deleteReply();
|
||||||
|
interaction.cleanUp();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the video format is not one compatible with Discord, reencode it.
|
// If the video format is not one compatible with Discord, reencode it unless autocrop is choosen in which case it gets reencoded anyway.
|
||||||
const bannedFormats = ['hevc'];
|
if (!interaction.doAutocrop) {
|
||||||
const codec = await utils.getVideoCodec(output);
|
const bannedFormats = ['av1'];
|
||||||
|
const codec = await utils.getVideoCodec(output);
|
||||||
|
|
||||||
if (bannedFormats.includes(codec)) {
|
if (bannedFormats.includes(codec)) {
|
||||||
console.log('Reencoding video');
|
const oldOutput = output;
|
||||||
|
output = `${os.tmpdir()}/264${file}`;
|
||||||
|
await utils.ffmpeg(['-i', oldOutput, '-vcodec', 'libx264', '-acodec', 'aac', output]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (interaction.doAutocrop && !compressInteraction.doCompress) {
|
||||||
const oldOutput = output;
|
const oldOutput = output;
|
||||||
output = `${os.tmpdir()}/264${file}`;
|
output = `${os.tmpdir()}/autocrop${file}`;
|
||||||
await utils.ffmpeg(`-i ${oldOutput} -vcodec libx264 -acodec aac ${output}`);
|
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 > 8) {
|
else if (fileSize > maxFileSize) {
|
||||||
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.followUp({ content: fileurl, 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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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) cleanUp();
|
|
||||||
|
if (interaction.isMessage) {
|
||||||
|
interaction.deleteReply();
|
||||||
|
interaction.cleanUp();
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(async err => {
|
.catch(async err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
@ -206,17 +267,68 @@ async function download(url, interaction, originalInteraction) {
|
||||||
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?
|
// Delete the file as it apparently don't overwrite?
|
||||||
fs.rmSync(output);
|
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;
|
||||||
|
|
||||||
if (fileSize > 8) {
|
embed.setAuthor({ name: `${embed.data.author.name} (${fileSize.toFixed(2)} MB)`, iconURL: embed.data.author.icon_url, url: embed.data.author.url });
|
||||||
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));
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
|
@ -18,7 +18,7 @@ export default {
|
||||||
category: 'utility',
|
category: 'utility',
|
||||||
async execute(interaction, args) {
|
async execute(interaction, args) {
|
||||||
const Embed = new EmbedBuilder()
|
const Embed = new EmbedBuilder()
|
||||||
.setAuthor({ name: `${interaction.user.tag} (${interaction.user.id})`, iconURL: interaction.user.avatarURL() })
|
.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.addFields({ name: 'Guild', value: `${interaction.guild.name} (${interaction.guild.id})`, inline: true });
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { SlashCommandBuilder, EmbedBuilder, AttachmentBuilder, PermissionsBitField } from 'discord.js';
|
import { SlashCommandBuilder, EmbedBuilder, AttachmentBuilder, PermissionsBitField } from 'discord.js';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
|
import ratelimiter from '../../utils/ratelimiter.js';
|
||||||
|
|
||||||
const { ownerId, prefix } = process.env;
|
const { ownerId, prefix } = process.env;
|
||||||
const prefixs = prefix.split(',');
|
const prefixs = prefix.split(',');
|
||||||
|
@ -113,6 +114,13 @@ export default {
|
||||||
embed.addFields({ name: 'Bot permission', value: `\`${perm.join('` `')}\``, inline: true });
|
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`)) {
|
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`);
|
const file = new AttachmentBuilder(`./asset/img/command/${command.category}/${command.data.name}.png`);
|
||||||
embed.setImage(`attachment://${command.data.name}.png`);
|
embed.setImage(`attachment://${command.data.name}.png`);
|
||||||
|
|
|
@ -8,6 +8,7 @@ export default {
|
||||||
.setDescription('The bot you want to make an invite link for.')
|
.setDescription('The bot you want to make an invite link for.')
|
||||||
.setRequired(false)),
|
.setRequired(false)),
|
||||||
category: 'utility',
|
category: 'utility',
|
||||||
|
integration_types: [0, 1],
|
||||||
async execute(interaction, args, client) {
|
async execute(interaction, args, client) {
|
||||||
if (args.bot) {
|
if (args.bot) {
|
||||||
if (args.bot.user.bot) {
|
if (args.bot.user.bot) {
|
||||||
|
@ -18,7 +19,9 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return interaction.reply(`You can add me from here: https://discord.com/oauth2/authorize?client_id=${client.user.id}&permissions=2684406848&scope=bot%20applications.commands`);
|
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}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,42 +6,63 @@ export default {
|
||||||
.setName('optout')
|
.setName('optout')
|
||||||
.setDescription('Opt out of the non commands features and arguments logging (for debugging purposes)'),
|
.setDescription('Opt out of the non commands features and arguments logging (for debugging purposes)'),
|
||||||
category: 'utility',
|
category: 'utility',
|
||||||
|
integration_types: [0, 1],
|
||||||
|
|
||||||
async execute(interaction, args, client) {
|
async execute(interaction, args, client) {
|
||||||
const isOptOut = await db.optout.findOne({ where: { userID: interaction.user.id } });
|
const isOptOut = await db.optout.findOne({ where: { userID: interaction.user.id } });
|
||||||
|
|
||||||
if (!isOptOut) {
|
if (!isOptOut) {
|
||||||
const body = { userID: interaction.user.id };
|
const body = { userID: interaction.user.id };
|
||||||
await db.optout.create(body);
|
await db.optout.create(body);
|
||||||
return await interaction.reply({ content: 'You have successfully been opt out.' });
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
const row = new ActionRowBuilder()
|
return interaction.followUp({
|
||||||
.addComponents(
|
content:
|
||||||
new ButtonBuilder()
|
'As a reminder here what opting out does:\n'
|
||||||
.setCustomId(`yes${interaction.user.id}`)
|
+ '- Your user ID will no longer be used for debug logging.\n'
|
||||||
.setLabel('Yes')
|
+ '- servers will no longer be shown in added/kicked stats.\n'
|
||||||
.setStyle(ButtonStyle.Primary),
|
+ '- Your messages won\'t be quoted.\n'
|
||||||
)
|
+ '- Won\'t show the arguments from commands.',
|
||||||
.addComponents(
|
ephemeral: true,
|
||||||
new ButtonBuilder()
|
},
|
||||||
.setCustomId(`no${interaction.user.id}`)
|
);
|
||||||
.setLabel('No')
|
|
||||||
.setStyle(ButtonStyle.Danger),
|
|
||||||
);
|
|
||||||
|
|
||||||
await interaction.reply({ content: 'You are already opt out, do you wish to opt in?', components: [row] });
|
|
||||||
|
|
||||||
client.on('interactionCreate', async (interactionMenu) => {
|
|
||||||
if (interaction.user !== interactionMenu.user) return;
|
|
||||||
if (!interactionMenu.isButton) return;
|
|
||||||
interactionMenu.update({ components: [] });
|
|
||||||
if (interactionMenu.customId === `yes${interaction.user.id}`) {
|
|
||||||
await db.optout.destroy({ where: { userID: interaction.user.id } });
|
|
||||||
return interaction.editReply('You have successfully been opt in');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return interaction.editReply('Nothing has been changed.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
async function listenButton(client, interaction, user = interaction.user, originalId = interaction.id) {
|
||||||
|
client.once('interactionCreate', async (interactionMenu) => {
|
||||||
|
if (user !== interactionMenu.user) return listenButton(client, interaction, user, originalId);
|
||||||
|
if (!interactionMenu.isButton()) return;
|
||||||
|
|
||||||
|
await interactionMenu.update({ components: [] });
|
||||||
|
|
||||||
|
if (interactionMenu.customId === `yes${interaction.user.id}${originalId}`) {
|
||||||
|
db.optout.destroy({ where: { userID: interaction.user.id } });
|
||||||
|
return interaction.editReply({ content: 'You have successfully been opt in', ephemeral: true });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -5,8 +5,9 @@ export default {
|
||||||
.setName('ping')
|
.setName('ping')
|
||||||
.setDescription('Replies with Pong!'),
|
.setDescription('Replies with Pong!'),
|
||||||
category: 'utility',
|
category: 'utility',
|
||||||
async execute(interaction) {
|
integration_types: [0, 1],
|
||||||
|
|
||||||
|
async execute(interaction) {
|
||||||
const row = new ActionRowBuilder()
|
const row = new ActionRowBuilder()
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
|
|
|
@ -36,13 +36,13 @@ export default {
|
||||||
const statsEmbed = new EmbedBuilder()
|
const statsEmbed = new EmbedBuilder()
|
||||||
.setColor(interaction.member ? interaction.member.displayHexColor : 'Navy')
|
.setColor(interaction.member ? interaction.member.displayHexColor : 'Navy')
|
||||||
.setTitle('Bot stats')
|
.setTitle('Bot stats')
|
||||||
.setAuthor({ name: client.user.tag, iconURL: client.user.displayAvatarURL(), url: 'https://libtar.de' })
|
.setAuthor({ name: client.user.username, iconURL: client.user.displayAvatarURL(), url: 'https://libtar.de' })
|
||||||
.addFields(
|
.addFields(
|
||||||
{ name: 'Servers', value: client.guilds.cache.size.toString(), inline: true },
|
{ name: 'Servers', value: client.guilds.cache.size.toString(), inline: true },
|
||||||
{ name: 'Channels', value: client.channels.cache.size.toString(), inline: true },
|
{ name: 'Channels', value: client.channels.cache.size.toString(), inline: true },
|
||||||
{ name: 'Users', value: client.users.cache.size.toString(), inline: true },
|
{ name: 'Users', value: client.users.cache.size.toString(), inline: true },
|
||||||
{ name: 'Ram usage', value: `${bytesToSize(process.memoryUsage().heapUsed)}/${bytesToSize(os.totalmem)}`, inline: true },
|
{ name: 'Ram usage', value: `${bytesToSize(process.memoryUsage().heapUsed)}/${bytesToSize(os.totalmem)}`, inline: true },
|
||||||
{ name: 'CPU', value: `${os.cpus()[0].model} (${os.cpus().length} core)`, inline: true },
|
{ name: 'CPU', value: `${os.cpus()[0].model} (${os.cpus().length} core) (${os.arch()})`, inline: true },
|
||||||
{ name: 'OS', value: `${os.platform()} ${os.release()}`, inline: true },
|
{ name: 'OS', value: `${os.platform()} ${os.release()}`, inline: true },
|
||||||
{ name: 'Nodejs version', value: process.version, inline: true },
|
{ name: 'Nodejs version', value: process.version, inline: true },
|
||||||
{ name: 'Discord.js version', value: version, inline: true },
|
{ name: 'Discord.js version', value: version, inline: true },
|
||||||
|
|
|
@ -20,7 +20,7 @@ export default {
|
||||||
}
|
}
|
||||||
const Embed = new EmbedBuilder()
|
const Embed = new EmbedBuilder()
|
||||||
.setColor(member ? member.displayHexColor : 'Navy')
|
.setColor(member ? member.displayHexColor : 'Navy')
|
||||||
.setAuthor({ name: `${user.tag} (${user.id})`, iconURL: user.displayAvatarURL() })
|
.setAuthor({ name: `${user.username} (${user.id})`, iconURL: user.displayAvatarURL() })
|
||||||
.addFields(
|
.addFields(
|
||||||
{ name: 'Current rank hex color', value: member ? member.displayHexColor : 'No rank color', inline: true },
|
{ 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: 'Joined guild at', value: member ? member.joinedAt.toString() : 'Not in this guild', inline: true },
|
||||||
|
|
|
@ -3,9 +3,9 @@ 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 { exec } from 'node:child_process';
|
import { execFile } from 'node:child_process';
|
||||||
const { NODE_ENV } = process.env;
|
const { NODE_ENV } = process.env;
|
||||||
|
const ytdlpFormat = 'bestvideo[height<=?480]/best';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
|
@ -14,44 +14,101 @@ 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 =>
|
||||||
|
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',
|
category: 'utility',
|
||||||
|
alias: ['v2g', 'togif'],
|
||||||
|
integration_types: [0, 1],
|
||||||
|
|
||||||
async execute(interaction, args) {
|
async execute(interaction, args) {
|
||||||
await interaction.deferReply({ ephemeral: false });
|
await interaction.deferReply({ ephemeral: false });
|
||||||
|
const maxFileSize = await utils.getMaxFileSize(interaction.guild);
|
||||||
const url = args.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 });
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.downloadVideo(url, interaction.id)
|
const aproxFileSize = await utils.getVideoSize(url, ytdlpFormat);
|
||||||
|
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));
|
||||||
const output = `${os.tmpdir()}/${file}`;
|
let 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}*`);
|
await gifski(gifskiOutput, `${os.tmpdir()}/frame${interaction.id}*`, quality, await args.fps);
|
||||||
// Optimize it
|
// Optimize it
|
||||||
await gifsicle(gifskiOutput, gifsicleOutput);
|
await gifsicle(gifskiOutput, gifsicleOutput, args.noloop);
|
||||||
|
|
||||||
const fileStat = fs.statSync(gifsicleOutput);
|
const fileStat = fs.statSync(gifsicleOutput);
|
||||||
const fileSize = fileStat.size / 1000000.0;
|
const fileSize = fileStat.size / 1000000.0;
|
||||||
|
|
||||||
if (fileSize > 100) {
|
if (fileSize > 25) {
|
||||||
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 > 8) {
|
else if (fileSize > maxFileSize) {
|
||||||
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 8 mb. It has been uploaded to an external site.\n${fileURL}`, ephemeral: false });
|
await interaction.editReply({ content: `ℹ️ File was bigger than ${maxFileSize} 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 });
|
||||||
|
@ -60,9 +117,10 @@ export default {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
async function gifski(output, input) {
|
async function gifski(output, input, quality, fps) {
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
exec(`gifski --quality 70 -o ${output} ${input}`, (err, stdout, stderr) => {
|
// Shell: true should be fine as no user input is being passed
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
@ -75,9 +133,10 @@ async function gifski(output, input) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function gifsicle(input, output) {
|
async function gifsicle(input, output, loop = false) {
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
exec(`gifsicle --colors 256 -i ${input} -o ${output}`, (err, stdout, stderr) => {
|
// Shell: true should be fine as no user input is being passed
|
||||||
|
execFile('gifsicle', ['--colors', '256', loop ? '--no-loopcount' : '', '-i', input, '-o', output], { shell: true }, (err, stdout, stderr) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(stderr);
|
reject(stderr);
|
||||||
}
|
}
|
||||||
|
@ -89,3 +148,19 @@ async function gifsicle(input, output) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
101
eslint.config.mjs
Normal file
101
eslint.config.mjs
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
}];
|
|
@ -7,6 +7,7 @@ const { statusChannel, NODE_ENV } = process.env;
|
||||||
export default {
|
export default {
|
||||||
name: 'guildDelete',
|
name: 'guildDelete',
|
||||||
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 } });
|
const isOptOut = await db.optout.findOne({ where: { userID: guildOwner.id } });
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// TODO: Moving that to a dedicated function that works for both messages and interactions
|
||||||
|
|
||||||
import { PermissionFlagsBits, InteractionType } 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';
|
import ratelimiter from '../../utils/ratelimiter.js';
|
||||||
|
@ -12,6 +14,15 @@ export default {
|
||||||
|
|
||||||
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 });
|
||||||
}
|
}
|
||||||
|
@ -19,7 +30,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.tag;
|
const userTag = interaction.user.username;
|
||||||
const userID = interaction.user.id;
|
const userID = interaction.user.id;
|
||||||
const commandName = interaction.commandName;
|
const commandName = interaction.commandName;
|
||||||
|
|
||||||
|
@ -27,21 +38,26 @@ export default {
|
||||||
|
|
||||||
if (!command) return;
|
if (!command) return;
|
||||||
|
|
||||||
const isOptOut = await db.optout.findOne({ where: { userID: interaction.user.id } });
|
let isOptOut = await db.optout.findOne({ where: { userID: interaction.user.id } });
|
||||||
|
|
||||||
if (isOptOut) {
|
if (commandName === 'optout') {
|
||||||
console.log(`A user launched command \x1b[33m${commandName}\x1b[0m with slash`);
|
isOptOut = true;
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.log(`\x1b[33m${userTag} (${userID})\x1b[0m launched command \x1b[33m${commandName}\x1b[0m with slash`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.default_permission) {
|
||||||
const clientMember = await interaction.guild.members.fetch(client.user.id);
|
const clientMember = await interaction.guild.members.fetch(client.user.id);
|
||||||
|
@ -59,8 +75,18 @@ export default {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Check if the limit of parallel execution has been reached
|
||||||
|
if (command.parallelLimit) {
|
||||||
|
const doParallelLimit = await ratelimiter.checkParallel(interaction.user, commandName, command);
|
||||||
|
if (doParallelLimit.limited) {
|
||||||
|
return await interaction.reply({ content: doParallelLimit.msg, ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
ratelimiter.addParallel(commandName);
|
||||||
|
}
|
||||||
|
|
||||||
// Check the ratelimit
|
// Check the ratelimit
|
||||||
const doRateLimit = ratelimiter.check(interaction.user, commandName, command);
|
const doRateLimit = await ratelimiter.check(interaction.user, commandName, command);
|
||||||
if (doRateLimit) {
|
if (doRateLimit) {
|
||||||
return interaction.reply({ content: doRateLimit, ephemeral: true });
|
return interaction.reply({ content: doRateLimit, ephemeral: true });
|
||||||
|
|
||||||
|
@ -83,14 +109,20 @@ export default {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isOptOut) {
|
if (!isOptOut) {
|
||||||
console.log(`\x1b[33m${commandName}\x1b[0m with args ${JSON.stringify(args)}`);
|
console.log(`[${timestamp.toISOString()}] \x1b[33m⤷\x1b[0m with args ${JSON.stringify(args)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await command.execute(interaction, args, client);
|
await command.execute(interaction, args, client)
|
||||||
|
.then(async () => {
|
||||||
|
const hasPrallelLimit = await ratelimiter.checkParallel(interaction.user, commandName, command);
|
||||||
|
if (hasPrallelLimit) ratelimiter.removeParallel(commandName);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
await interaction.followUp({ content: 'There was an error while executing this command!', ephemeral: true });
|
const hasPrallelLimit = await ratelimiter.checkParallel(interaction.user, commandName, command);
|
||||||
|
if (hasPrallelLimit) ratelimiter.removeParallel(commandName);
|
||||||
|
await interaction.followUp({ content: `There was an error while executing this command!\n\`${error}\``, ephemeral: true });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -253,6 +253,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Command handling from message
|
// Command handling from message
|
||||||
|
// TODO: Moving that to a dedicated function that works for both messages and interactions
|
||||||
|
|
||||||
let hasPrefix = false;
|
let hasPrefix = false;
|
||||||
prefixs.forEach(p => {
|
prefixs.forEach(p => {
|
||||||
|
@ -283,6 +284,14 @@ export default {
|
||||||
const globalBlacklist = await db.Blacklists.findOne({ where: { type:'global', uid:message.author.id } });
|
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 } });
|
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) {
|
if (globalBlacklist) {
|
||||||
return message.reply({ content: `You are globally blacklisted for the following reason: \`${globalBlacklist.reason}\``, ephemeral: true });
|
return message.reply({ content: `You are globally blacklisted for the following reason: \`${globalBlacklist.reason}\``, ephemeral: true });
|
||||||
}
|
}
|
||||||
|
@ -290,23 +299,29 @@ export default {
|
||||||
return message.reply({ content: `You are blacklisted for the following reason: \`${commandBlacklist.reason}\``, ephemeral: true });
|
return message.reply({ content: `You are blacklisted for the following reason: \`${commandBlacklist.reason}\``, ephemeral: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
const userTag = message.author.tag;
|
const userTag = message.author.username;
|
||||||
const userID = message.author.id;
|
const userID = message.author.id;
|
||||||
|
|
||||||
const isOptOut = await db.optout.findOne({ where: { userID: message.author.id } });
|
let isOptOut = await db.optout.findOne({ where: { userID: message.author.id } });
|
||||||
|
|
||||||
if (isOptOut) {
|
if (commandName === 'optout') {
|
||||||
console.log(`A user launched command \x1b[33m${commandName}\x1b[0m with prefix`);
|
isOptOut = true;
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.log(`\x1b[33m${userTag} (${userID})\x1b[0m launched command \x1b[33m${commandName}\x1b[0m with prefix`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// Owner only check
|
||||||
if (command.ownerOnly && message.author.id !== ownerId) {
|
if (command.ownerOnly && message.author.id !== ownerId) {
|
||||||
return message.reply({ content: '❌ This command is reserved for the owner!', ephemeral: true });
|
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
|
// Check if the bot has the needed permissions
|
||||||
if (command.clientPermissions) {
|
if (command.clientPermissions) {
|
||||||
const clientMember = await message.guild.members.fetch(client.user.id);
|
const clientMember = await message.guild.members.fetch(client.user.id);
|
||||||
|
@ -322,8 +337,18 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the limit of parallel execution has been reached
|
||||||
|
if (command.parallelLimit) {
|
||||||
|
const doParallelLimit = await ratelimiter.checkParallel(message.author, commandName, command);
|
||||||
|
if (doParallelLimit.limited) {
|
||||||
|
return await message.reply({ content: doParallelLimit.msg, ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
ratelimiter.addParallel(commandName);
|
||||||
|
}
|
||||||
|
|
||||||
// Check the ratelimit
|
// Check the ratelimit
|
||||||
const doRateLimit = ratelimiter.check(message.author, commandName, command);
|
const doRateLimit = await ratelimiter.check(message.author, commandName, command);
|
||||||
if (doRateLimit) {
|
if (doRateLimit) {
|
||||||
return message.reply({ content: doRateLimit, ephemeral: true });
|
return message.reply({ content: doRateLimit, ephemeral: true });
|
||||||
|
|
||||||
|
@ -377,11 +402,17 @@ export default {
|
||||||
});
|
});
|
||||||
|
|
||||||
const argsLength = command.data.options.length - argsToDelete;
|
const argsLength = command.data.options.length - argsToDelete;
|
||||||
|
const missingRequired = [];
|
||||||
|
|
||||||
for (let i = 0, j = 0; i < argsLength; i++, j++) {
|
for (let i = 0, j = 0; i < argsLength; i++, j++) {
|
||||||
if (!messageArgs[i]) continue;
|
|
||||||
const arg = command.data.options[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;
|
if (arg.type === ApplicationCommandOptionType.Attachment) continue;
|
||||||
|
|
||||||
let payloadName = arg.name;
|
let payloadName = arg.name;
|
||||||
|
@ -391,13 +422,16 @@ export default {
|
||||||
payload = messageArgs.slice(i).join(' ');
|
payload = messageArgs.slice(i).join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (messageArgs[i].startsWith('--')) {
|
if (arg.type === ApplicationCommandOptionType.Boolean && !messageArgs[i].startsWith('--')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (messageArgs[i].startsWith('--')) {
|
||||||
payloadName = payload.substring(2);
|
payloadName = payload.substring(2);
|
||||||
payload = true;
|
payload = true;
|
||||||
j--;
|
j--;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg.type === ApplicationCommandOptionType.Mentionable) {
|
if (arg.type === ApplicationCommandOptionType.Mentionable || arg.type === ApplicationCommandOptionType.User) {
|
||||||
await message.guild.members.fetch();
|
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()));
|
payload = message.mentions.members.first() ? message.mentions.members.first() : message.guild.members.cache.find(u => u.user.username.toLowerCase().includes(payload.toLowerCase()));
|
||||||
}
|
}
|
||||||
|
@ -405,15 +439,33 @@ export default {
|
||||||
args[payloadName] = payload;
|
args[payloadName] = payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isOptOut) {
|
if (!isOptOut && argsLength > 0) {
|
||||||
console.log(`\x1b[33m${commandName}\x1b[0m with args ${JSON.stringify(args)}`);
|
console.log(`[${timestamp.toISOString()}] \x1b[33m⤷\x1b[0m with args ${JSON.stringify(args)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await command.execute(message, args, client);
|
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) {
|
catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
await message.reply({ content: 'There was an error while executing this command!', ephemeral: true });
|
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}\`` });
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -128,7 +128,7 @@ export default {
|
||||||
.setFooter({ text: `${emote} ${reactionCount}` })
|
.setFooter({ text: `${emote} ${reactionCount}` })
|
||||||
.setTimestamp();
|
.setTimestamp();
|
||||||
|
|
||||||
if (reaction.message.guild.emojis.resolve(emote)) Embed.setFooter(reactionCount, reaction.message.guild.emojis.resolve(emote).url);
|
if (reaction.message.guild.emojis.resolve(emote)) Embed.setFooter({ text: `${reactionCount}`, iconURL: reaction.message.guild.emojis.resolve(emote).url });
|
||||||
|
|
||||||
let description = null;
|
let description = null;
|
||||||
|
|
||||||
|
|
|
@ -111,7 +111,7 @@ export default {
|
||||||
.setFooter({ text: `${emote} ${reactionCount}` })
|
.setFooter({ text: `${emote} ${reactionCount}` })
|
||||||
.setTimestamp();
|
.setTimestamp();
|
||||||
|
|
||||||
if (reaction.message.guild.emojis.resolve(emote)) Embed.setFooter(reactionCount, reaction.message.guild.emojis.resolve(emote).url);
|
if (reaction.message.guild.emojis.resolve(emote)) Embed.setFooter({ text: `${reactionCount}`, iconURL: reaction.message.guild.emojis.resolve(emote).url });
|
||||||
|
|
||||||
message.edit({ embeds: [Embed] });
|
message.edit({ embeds: [Embed] });
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { exec } from 'node:child_process';
|
import { execFile } from 'node:child_process';
|
||||||
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',
|
||||||
|
@ -8,8 +9,21 @@ export default {
|
||||||
// Init global variables.
|
// Init global variables.
|
||||||
global.boards = {};
|
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) => {
|
||||||
exec('./bin/yt-dlp --version', (err, stdout, stderr) => {
|
execFile('./bin/yt-dlp', ['--version'], (err, stdout, stderr) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(stderr);
|
reject(stderr);
|
||||||
}
|
}
|
||||||
|
@ -20,24 +34,17 @@ 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(`Ready to serve in ${channelSize} channels on ${guildSize} servers.\nThere is ${commandSize} command loaded.\nRunning yt-dlp ${ytdlpVersion.replace('\n', '')}\n${client.readyAt}`);
|
channel.send(
|
||||||
|
`Ready to serve in ${channelSize} channels on ${guildSize} servers.\n` +
|
||||||
|
`There is ${commandSize} command loaded.\n` +
|
||||||
|
`Running yt-dlp ${ytdlpVersion.replace('\n', '')}\n` +
|
||||||
|
`${client.readyAt}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
|
@ -1,38 +1,51 @@
|
||||||
import game from '../../json/playing.json' assert {type: 'json'};
|
import { ActivityType } from 'discord.js';
|
||||||
import watch from '../../json/watching.json' assert {type: 'json'};
|
import game from '../../json/playing.json' with {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
|
||||||
setStatus();
|
await setStatus();
|
||||||
// Change status every 30 minutes
|
// Change status every 30 minutes
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
setStatus();
|
await setStatus();
|
||||||
}, 1800000);
|
}, 1800000);
|
||||||
|
|
||||||
async function setStatus() {
|
async function setStatus() {
|
||||||
const random = Math.floor((Math.random() * 2));
|
const random = Math.floor((Math.random() * 3));
|
||||||
|
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');
|
||||||
|
|
||||||
let status = watch[Math.floor((Math.random() * watch.length))];
|
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}`);
|
||||||
client.user.setActivity(status, { type: 'WATCHING' });
|
types = [ ActivityType.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');
|
||||||
|
|
||||||
let status = game[Math.floor((Math.random() * game.length))];
|
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}`);
|
||||||
client.user.setActivity(status, { type: 'PLAYING' });
|
types = [ ActivityType.Playing, ActivityType.Competing ];
|
||||||
}
|
}
|
||||||
|
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))] });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
8
index.js
8
index.js
|
@ -1,9 +1,7 @@
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||||
import { Client, Collection, GatewayIntentBits, Partials } from 'discord.js';
|
import { Client, Collection, GatewayIntentBits, Partials } from 'discord.js';
|
||||||
import dotenv from 'dotenv';
|
|
||||||
dotenv.config();
|
|
||||||
const { token, NODE_ENV } = process.env;
|
const { token, NODE_ENV } = process.env;
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
@ -41,7 +39,7 @@ 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(filePath)
|
import(pathToFileURL(filePath))
|
||||||
.then(importedCommand => {
|
.then(importedCommand => {
|
||||||
const command = importedCommand.default;
|
const command = importedCommand.default;
|
||||||
client.commands.set(command.data.name, command);
|
client.commands.set(command.data.name, command);
|
||||||
|
@ -57,7 +55,7 @@ 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(filePath)
|
import(pathToFileURL(filePath))
|
||||||
.then(importedEvent => {
|
.then(importedEvent => {
|
||||||
const event = importedEvent.default;
|
const event = importedEvent.default;
|
||||||
if (event.once) {
|
if (event.once) {
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
["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."]
|
["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."]
|
3
json/listening.json
Normal file
3
json/listening.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[
|
||||||
|
"psychometricBussdown by oddballTheatre"
|
||||||
|
]
|
4730
package-lock.json
generated
4730
package-lock.json
generated
File diff suppressed because it is too large
Load diff
32
package.json
32
package.json
|
@ -4,9 +4,9 @@
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node .",
|
"start": "node --env-file .env .",
|
||||||
"deploy": "node deploy-commands.cjs",
|
"deploy": "node --env-file .env scripts/deploy-commands.js",
|
||||||
"deployGlobally": "node deploy-commands.cjs global",
|
"deployGlobally": "node --env-file .env scripts/deploy-commands.js 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,25 @@
|
||||||
"homepage": "https://libtar.de",
|
"homepage": "https://libtar.de",
|
||||||
"license": "AGPL",
|
"license": "AGPL",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordjs/rest": "^0.4.1",
|
"@discordjs/rest": "^2.3.0",
|
||||||
"discord-api-types": "^0.33.5",
|
"discord-api-types": "^0.37.91",
|
||||||
"discord.js": "^14.9.0",
|
"discord.js": "^14.15.3",
|
||||||
"dotenv": "^16.0.1",
|
"mariadb": "^3.3.1",
|
||||||
"mariadb": "^3.0.1",
|
"node-fetch": "^3.3.2",
|
||||||
"mysql2": "^2.3.3",
|
"sequelize": "^6.37.3",
|
||||||
"node-fetch": "^3.2.6",
|
"turndown": "^7.2.0",
|
||||||
"safe-regex": "github:davisjam/safe-regex",
|
"twitter-api-v2": "^1.17.1",
|
||||||
"sequelize": "^6.21.3",
|
|
||||||
"turndown": "^7.1.1",
|
|
||||||
"twit": "^1.1.20",
|
|
||||||
"ytpplus-node": "github:Supositware/ytpplus-node"
|
"ytpplus-node": "github:Supositware/ytpplus-node"
|
||||||
},
|
},
|
||||||
"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.16.0",
|
"eslint": "^8.57.0",
|
||||||
|
"globals": "^15.8.0",
|
||||||
"sequelize-cli": "^6.4.1",
|
"sequelize-cli": "^6.4.1",
|
||||||
"sqlite3": "^5.1.6"
|
"sqlite3": "^5.1.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
readme.md
10
readme.md
|
@ -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 (Optional but very recommanded: for yt-dlp to merge video/audio formats and Handbrake to compress videos.)
|
* ffmpeg & ffprobe (Optional but very recommanded: for yt-dlp to merge video/audio formats and Handbrake to compress videos.)
|
||||||
* yt-dlp ([a file can download it for you](scripts/updateytdlp.js))
|
* yt-dlp ([a file can download it for you](scripts/updateytdlp.js))
|
||||||
* 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 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)
|
* 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)
|
||||||
|
|
||||||
### 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 scripts/deploy-commands.cjs``
|
``node --env-file .env scripts/deploy-commands.cjs``
|
||||||
|
|
||||||
then you can just run it normally.
|
then you can just run it normally.
|
||||||
``node index.js``
|
``node --env-file .env 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 index.js &``
|
``nohup node --env-file .env index.js &``
|
||||||
|
|
||||||
## Built With
|
## Built With
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,7 @@ import { REST } from '@discordjs/rest';
|
||||||
import { Routes } from 'discord-api-types/v9';
|
import { Routes } from 'discord-api-types/v9';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||||
import dotenv from 'dotenv';
|
|
||||||
dotenv.config();
|
|
||||||
const { clientId, guildId, token } = process.env;
|
const { clientId, guildId, token } = process.env;
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
@ -17,7 +15,12 @@ for (let i = 0; i < categoryPath.length; i++) {
|
||||||
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
|
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
|
||||||
for (const file of commandFiles) {
|
for (const file of commandFiles) {
|
||||||
const filePath = path.join(commandsPath, file);
|
const filePath = path.join(commandsPath, file);
|
||||||
const command = await import(filePath);
|
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());
|
commands.push(command.default.data.toJSON());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,11 +23,11 @@ async function download(url, output) {
|
||||||
fs.renameSync(tmpPath, path);
|
fs.renameSync(tmpPath, path);
|
||||||
fs.chmodSync(path, '755');
|
fs.chmodSync(path, '755');
|
||||||
console.log(`${url} download finished.`);
|
console.log(`${url} download finished.`);
|
||||||
resolve(true);
|
return resolve(true);
|
||||||
});
|
});
|
||||||
filePath.on('error', (err) => {
|
filePath.on('error', (err) => {
|
||||||
filePath.close();
|
filePath.close();
|
||||||
reject(err);
|
return reject(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
import dotenv from 'dotenv';
|
|
||||||
import fetch from 'node-fetch';
|
import fetch from 'node-fetch';
|
||||||
import { Client, GatewayIntentBits } from 'discord.js';
|
import { Client, GatewayIntentBits } from 'discord.js';
|
||||||
|
|
||||||
|
|
||||||
dotenv.config();
|
|
||||||
const { botsggToken, botsggEndpoint, token } = process.env;
|
const { botsggToken, botsggEndpoint, token } = process.env;
|
||||||
|
|
||||||
const client = new Client({
|
const client = new Client({
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
|
// This is kind of useless since you can just do `./yt-dlp --update-to nightly` which I didn't know about when I wrote that.
|
||||||
import utils from './downloadutils.js';
|
import utils from './downloadutils.js';
|
||||||
|
|
||||||
if (process.platform !== 'linux' && process.argv[2] !== '-f') {
|
(async () => {
|
||||||
console.error('This script only download the linux version of yt-dlp. If you want to download anyway try again with -f');
|
if (process.platform !== 'linux' && process.argv[2] !== '-f') {
|
||||||
process.exit(1);
|
console.error('This script only download the linux version of yt-dlp. If you want to download anyway try again with -f or execute ./bin/yt-dlp --update-to nightly');
|
||||||
}
|
process.exit(1);
|
||||||
else if (process.platform !== 'linux' && process.argv[2] === '-f') {
|
}
|
||||||
console.log('Executed with -f. Reminder that this script only download the linux version of yt-dlp.');
|
else if (process.platform !== 'linux' && process.argv[2] === '-f') {
|
||||||
}
|
console.log('Executed with -f. Reminder that this script only download the linux version of yt-dlp.');
|
||||||
|
}
|
||||||
|
|
||||||
const downloadUrl = 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp';
|
const downloadUrl = 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp';
|
||||||
|
|
||||||
utils.download(downloadUrl, './bin/yt-dlp');
|
await utils.download(downloadUrl, './bin/yt-dlp');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
0
tmp/.keep
Normal file
0
tmp/.keep
Normal file
|
@ -65,7 +65,9 @@ export function rand(text, interaction) {
|
||||||
const matches = text.matchAll(/\[.*?\]\s?/g);
|
const matches = text.matchAll(/\[.*?\]\s?/g);
|
||||||
|
|
||||||
for (const match of matches) {
|
for (const match of matches) {
|
||||||
if (search(match[0].trim(), variables)) { text = text.replace(match[0].trim(), search(match[0].trim(), variables).value); }
|
if (search(match[0].trim(), variables)) {
|
||||||
|
text = text.replace(match[0].trim(), search(match[0].trim(), variables).value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
|
|
|
@ -1,12 +1,25 @@
|
||||||
const ratelimit = {};
|
const ratelimit = {};
|
||||||
|
const parallelLimit = {};
|
||||||
|
const { ownerId, NODE_ENV } = process.env;
|
||||||
|
|
||||||
import db from '../models/index.js';
|
import db from '../models/index.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
check,
|
check,
|
||||||
|
addParallel,
|
||||||
|
removeParallel,
|
||||||
|
checkParallel,
|
||||||
};
|
};
|
||||||
function check(user, commandName, commands) {
|
async function check(user, commandName, commands) {
|
||||||
const userID = user.id;
|
const userID = user.id;
|
||||||
const userTag = user.tag;
|
const userTag = user.username;
|
||||||
|
|
||||||
|
// Don't apply the rate limit to bot owner
|
||||||
|
if (NODE_ENV !== 'development') {
|
||||||
|
if (user.id === ownerId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!ratelimit[userID]) {
|
if (!ratelimit[userID]) {
|
||||||
ratelimit[userID] = {};
|
ratelimit[userID] = {};
|
||||||
|
@ -27,13 +40,11 @@ function check(user, commandName, commands) {
|
||||||
const hours = Math.floor(minutes / 60);
|
const hours = Math.floor(minutes / 60);
|
||||||
const dateString = `${hours > 0 ? ` ${Math.floor(hours)} hours` : ''}${minutes > 0 ? ` ${Math.floor(minutes % 60)} minutes` : ''}${seconds > 0 ? ` ${Math.floor(seconds % 60)} seconds` : ''}`;
|
const dateString = `${hours > 0 ? ` ${Math.floor(hours)} hours` : ''}${minutes > 0 ? ` ${Math.floor(minutes % 60)} minutes` : ''}${seconds > 0 ? ` ${Math.floor(seconds % 60)} seconds` : ''}`;
|
||||||
|
|
||||||
const isOptOut = db.optout.findOne({ where: { userID: userID } });
|
const isOptOut = await db.optout.findOne({ where: { userID: userID } });
|
||||||
if (isOptOut) {
|
|
||||||
console.log(`A user is rate limited on \x1b[33m${commandName}\x1b[0m for${dateString}.`);
|
const timestamp = new Date();
|
||||||
}
|
console.log(`[${timestamp.toISOString()}] \x1b[33m${ isOptOut ? 'A user' : `${userTag} (${userID})`}\x1b[0m is rate limited on \x1b[33m${commandName}\x1b[0m for${dateString}.`);
|
||||||
else {
|
|
||||||
console.log(`\x1b[33m${userTag} (${userID})\x1b[0m is rate limited on \x1b[33m${commandName}\x1b[0m for${dateString}.`);
|
|
||||||
}
|
|
||||||
return `You are being rate limited. You can try again in${dateString}.`;
|
return `You are being rate limited. You can try again in${dateString}.`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,4 +56,48 @@ function check(user, commandName, commands) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addParallel(commandName) {
|
||||||
|
// console.log(`[ADD] Adding parallel to ${commandName}`);
|
||||||
|
if (!parallelLimit[commandName]) parallelLimit[commandName] = 0;
|
||||||
|
|
||||||
|
const prevNumber = parallelLimit[commandName];
|
||||||
|
|
||||||
|
// console.log(`[ADD] Previous parallel executions: ${prevNumber}`);
|
||||||
|
// console.log(`[ADD] Current parallel executions: ${JSON.stringify(parallelLimit)}`);
|
||||||
|
parallelLimit[commandName] = prevNumber + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeParallel(commandName) {
|
||||||
|
// console.log(`[REMOVE] Removing parallel to ${commandName}`);
|
||||||
|
|
||||||
|
// This shouldn't be possible
|
||||||
|
if (!parallelLimit[commandName]) parallelLimit[commandName] = 0;
|
||||||
|
|
||||||
|
const prevNumber = parallelLimit[commandName];
|
||||||
|
|
||||||
|
// console.log(`[REMOVE] previous number: ${prevNumber}`);
|
||||||
|
// console.log(`[REMOVE] previous parallel limit: ${JSON.stringify(parallelLimit)}`);
|
||||||
|
parallelLimit[commandName] = prevNumber - 1;
|
||||||
|
// console.log(`[REMOVE] current parallel limit: ${JSON.stringify(parallelLimit)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkParallel(user, commandName, command) {
|
||||||
|
// Don't apply the rate limit to bot owner
|
||||||
|
if (NODE_ENV !== 'development') {
|
||||||
|
if (user.id === ownerId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parallelLimit[commandName]) parallelLimit[commandName] = 0;
|
||||||
|
|
||||||
|
// console.log(`[CHECK] command limit: ${command.parallelLimit}`);
|
||||||
|
// console.log(`[CHECK] current parallel executions: ${parallelLimit[commandName]}`);
|
||||||
|
if (parallelLimit[commandName] >= command.parallelLimit) {
|
||||||
|
return { limited: true, current: parallelLimit[commandName], max: command.parallelLimit, msg: `There are currently too many parallel execution of this command, please wait before retrying. (${parallelLimit[commandName]}/${command.parallelLimit})` };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { limited: false, current: parallelLimit[commandName], max: command.parallelLimit };
|
||||||
}
|
}
|
112
utils/videos.js
112
utils/videos.js
|
@ -1,6 +1,6 @@
|
||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
import { exec } from 'node:child_process';
|
import { execFile } from 'node:child_process';
|
||||||
const { NODE_ENV } = process.env;
|
const { NODE_ENV, ytdlpMaxResolution, proxy } = process.env;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
downloadVideo,
|
downloadVideo,
|
||||||
|
@ -9,25 +9,35 @@ export default {
|
||||||
stringIsAValidurl,
|
stringIsAValidurl,
|
||||||
compressVideo,
|
compressVideo,
|
||||||
getVideoCodec,
|
getVideoCodec,
|
||||||
|
getVideoSize,
|
||||||
|
getMaxFileSize,
|
||||||
|
autoCrop,
|
||||||
};
|
};
|
||||||
async function downloadVideo(urlArg, output, format = 'bestvideo*+bestaudio/best') {
|
async function downloadVideo(urlArg, output, format = `bestvideo[height<=?${ytdlpMaxResolution}]+bestaudio/best`) {
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
exec(`./bin/yt-dlp -f ${format} "${urlArg}" -o "${os.tmpdir()}/${output}.%(ext)s" --force-overwrites --no-playlist --merge-output-format=mp4/webm/mov`, (err, stdout, stderr) => {
|
const options = ['-f', format, urlArg, '-o', `${os.tmpdir()}/${output}.%(ext)s`, '--force-overwrites', '--playlist-reverse', '--no-playlist', '--remux-video=mp4/webm/mov', '--no-warnings'];
|
||||||
|
if (proxy) {
|
||||||
|
options.push('--proxy');
|
||||||
|
options.push(proxy);
|
||||||
|
};
|
||||||
|
execFile('./bin/yt-dlp', options, (err, stdout, stderr) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(stderr);
|
return reject(stderr);
|
||||||
}
|
}
|
||||||
if (stderr) {
|
if (stderr) {
|
||||||
console.error(stderr);
|
console.error(stderr);
|
||||||
|
// Should already be rejected at that points
|
||||||
|
return reject(stderr);
|
||||||
}
|
}
|
||||||
console.log(NODE_ENV === 'development' ? stdout : null);
|
console.log(NODE_ENV === 'development' ? stdout : null);
|
||||||
resolve();
|
return resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function upload(file) {
|
async function upload(file) {
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
exec(`./bin/upload.sh ${file}`, (err, stdout, stderr) => {
|
execFile('./bin/upload.sh', [file], (err, stdout, stderr) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(stderr);
|
reject(stderr);
|
||||||
}
|
}
|
||||||
|
@ -41,7 +51,7 @@ async function upload(file) {
|
||||||
|
|
||||||
async function ffmpeg(command) {
|
async function ffmpeg(command) {
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
exec(`ffmpeg ${command}`, (err, stdout, stderr) => {
|
execFile('ffmpeg', ['-hide_banner', ...command], (err, stdout, stderr) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(stderr);
|
reject(stderr);
|
||||||
}
|
}
|
||||||
|
@ -66,7 +76,7 @@ async function stringIsAValidurl(s) {
|
||||||
|
|
||||||
async function compressVideo(input, output, preset) {
|
async function compressVideo(input, output, preset) {
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
exec(`./bin/HandBrakeCLI -i '${input}' -Z '${preset}' -o '${os.tmpdir()}/${output}'`, (err, stdout, stderr) => {
|
execFile('./bin/HandBrakeCLI', ['-i', input, '-Z', preset, '--turbo', '--optimize', '-o', `${os.tmpdir()}/${output}`], (err, stdout, stderr) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(stderr);
|
reject(stderr);
|
||||||
}
|
}
|
||||||
|
@ -80,7 +90,7 @@ async function compressVideo(input, output, preset) {
|
||||||
}
|
}
|
||||||
async function getVideoCodec(input) {
|
async function getVideoCodec(input) {
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
exec(`ffprobe -v error -select_streams v:0 -show_entries stream=codec_name -of default=noprint_wrappers=1:nokey=1 ${input}`, (err, stdout, stderr) => {
|
execFile('ffprobe', ['-v', 'error', '-select_streams', 'v:0', '-show_entries', 'stream=codec_name', '-of', 'default=noprint_wrappers=1:nokey=1', input], (err, stdout, stderr) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(stderr);
|
reject(stderr);
|
||||||
}
|
}
|
||||||
|
@ -90,4 +100,84 @@ async function getVideoCodec(input) {
|
||||||
resolve(stdout.trim());
|
resolve(stdout.trim());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getVideoSize(urlArg, format = `bestvideo[height<=?${ytdlpMaxResolution}]+bestaudio/best`) {
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
const options = [urlArg, '-f', format, '--no-warnings', '-O', '%(filesize,filesize_approx)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 / 1000000.0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getMaxFileSize(guild) {
|
||||||
|
return await new Promise((resolve) => {
|
||||||
|
if (!guild) {
|
||||||
|
resolve(25);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tier = guild.premiumTier;
|
||||||
|
switch (tier) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
resolve(25);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
resolve(50);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
resolve(100);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
resolve(25);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function autoCrop(input, output) {
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
let ffprobeInput = input;
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
// ffprobe 'movie=' options does not like windows absolute path
|
||||||
|
ffprobeInput = input.replace(/\\/g, '/').replace(/\:/g, '\\\\:');
|
||||||
|
}
|
||||||
|
|
||||||
|
execFile('ffprobe',
|
||||||
|
['-f', 'lavfi', '-i', `movie=${ffprobeInput},cropdetect`, '-show_entries',
|
||||||
|
'packet_tags=lavfi.cropdetect.w,lavfi.cropdetect.h,lavfi.cropdetect.x,lavfi.cropdetect.y',
|
||||||
|
'-read_intervals', '%+#10', '-hide_banner', '-print_format', 'json'], async (err, stdout, stderr) => {
|
||||||
|
if (err) {
|
||||||
|
reject(stderr);
|
||||||
|
}
|
||||||
|
if (stderr) {
|
||||||
|
console.error(stderr);
|
||||||
|
}
|
||||||
|
const packets = JSON.parse(stdout).packets;
|
||||||
|
|
||||||
|
for (let i = 0; i < packets.length; i++) {
|
||||||
|
const element = packets[i];
|
||||||
|
|
||||||
|
if (element.tags) {
|
||||||
|
const cropdetect = element.tags;
|
||||||
|
await ffmpeg(['-i', input, '-vf', `crop=${cropdetect['lavfi.cropdetect.w']}:${cropdetect['lavfi.cropdetect.h']}:${cropdetect['lavfi.cropdetect.x']}:${cropdetect['lavfi.cropdetect.y']}`, '-vcodec', 'libx264', '-acodec', 'aac', output]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(NODE_ENV === 'development' ? stdout : null);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue