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
|
||||
guildId=DevGuildId
|
||||
ownerId=OwnerUserId
|
||||
statusChannel=
|
||||
statusChannel=CHannelIdForStatus
|
||||
uptimeURL=UptimeKumaOrWhateverStatusThingYouUseOrJustLeaveEmpty
|
||||
uptimeInterval=60
|
||||
twiConsumer=TwitterConsumerToken
|
||||
|
@ -15,3 +15,6 @@ botsggToken=APITokenForBots.gg
|
|||
botsggEndpoint=https://discord.bots.gg/api/v1
|
||||
stableHordeApi=0000000000
|
||||
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?**
|
||||
|
||||
- [ ] 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
|
||||
|
|
11
.gitignore
vendored
11
.gitignore
vendored
|
@ -1,7 +1,16 @@
|
|||
.env
|
||||
node_modules/
|
||||
bin/
|
||||
config/config.json
|
||||
json/board/
|
||||
unloaded/
|
||||
database.sqlite3
|
||||
tmp/*.js
|
||||
|
||||
bin/yt-dlp*
|
||||
bin/HandBrakeCLI*
|
||||
bin/upload.sh
|
||||
bin/dectalk
|
||||
|
||||
asset/ytp/sources
|
||||
asset/ytp/music
|
||||
asset/ytp/sounds
|
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?')
|
||||
.setRequired(true)),
|
||||
category: 'AI',
|
||||
alias: ['i2i'],
|
||||
async execute(interaction, args, client) {
|
||||
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);
|
||||
|
||||
response = await response.json();
|
||||
|
||||
if (!response.id) {
|
||||
console.log(response);
|
||||
return i.editReply({ content: `An error has occured, please try again later. \`${response.message}\`` });
|
||||
}
|
||||
|
||||
let wait_time = 5000;
|
||||
let checkURL = `https://stablehorde.net/api/v2/generate/check/${response.id}`;
|
||||
const checking = setInterval(async () => {
|
||||
|
@ -84,8 +91,13 @@ async function generate(i, prompt, client, b64Img) {
|
|||
|
||||
if (checkResult === undefined) return;
|
||||
if (!checkResult.done) {
|
||||
if (checkResult.wait_time === -1) {
|
||||
console.log(checkResult.raw);
|
||||
return i.editReply({ content: `An error has occured, please try again later. \`${checkResult.raw.message}\`` });
|
||||
}
|
||||
|
||||
if (checkResult.wait_time < 0) {
|
||||
console.log(checkResult);
|
||||
console.log(checkResult.raw);
|
||||
clearInterval(checking);
|
||||
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()
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`regenerate${i.user.id}`)
|
||||
.setCustomId(`regenerate${i.user.id}${i.id}`)
|
||||
.setLabel('🔄 Regenerate')
|
||||
.setStyle(ButtonStyle.Primary),
|
||||
);
|
||||
|
||||
await i.editReply({ embeds: [stableEmbed], components: [row], files: [generatedImg] });
|
||||
|
||||
client.once('interactionCreate', async (interactionMenu) => {
|
||||
if (i.user !== interactionMenu.user) return;
|
||||
if (!interactionMenu.isButton) return;
|
||||
if (interactionMenu.customId === `regenerate${interactionMenu.user.id}`) {
|
||||
await interactionMenu.deferReply();
|
||||
await generate(interactionMenu, prompt, client);
|
||||
}
|
||||
});
|
||||
listenButton(client, i, prompt);
|
||||
}
|
||||
}, wait_time);
|
||||
}
|
||||
|
@ -140,14 +145,27 @@ async function checkGeneration(url) {
|
|||
check = await check.json();
|
||||
|
||||
if (!check.is_possible) {
|
||||
return { done: false, wait_time: -1 };
|
||||
return { done: false, wait_time: -1, raw: check };
|
||||
}
|
||||
|
||||
if (check.done) {
|
||||
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?')
|
||||
.setRequired(true)),
|
||||
category: 'AI',
|
||||
alias: ['t2i'],
|
||||
async execute(interaction, args, client) {
|
||||
await interaction.deferReply();
|
||||
generate(interaction, args.prompt, client);
|
||||
|
@ -101,21 +102,14 @@ async function generate(i, prompt, client) {
|
|||
const row = new ActionRowBuilder()
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`regenerate${i.user.id}`)
|
||||
.setCustomId(`regenerate${i.user.id}${i.id}`)
|
||||
.setLabel('🔄 Regenerate')
|
||||
.setStyle(ButtonStyle.Primary),
|
||||
);
|
||||
|
||||
await i.editReply({ embeds: [stableEmbed], components: [row], files: [generatedImg] });
|
||||
|
||||
client.once('interactionCreate', async (interactionMenu) => {
|
||||
if (i.user !== interactionMenu.user) return;
|
||||
if (!interactionMenu.isButton) return;
|
||||
if (interactionMenu.customId === `regenerate${interactionMenu.user.id}`) {
|
||||
await interactionMenu.deferReply();
|
||||
await generate(interactionMenu, prompt, client);
|
||||
}
|
||||
});
|
||||
listenButton(client, i, prompt);
|
||||
}
|
||||
}, 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 };
|
||||
}
|
||||
}
|
||||
|
||||
async function listenButton(client, interaction, prompt) {
|
||||
client.once('interactionCreate', async (interactionMenu) => {
|
||||
if (!interactionMenu.isButton()) return;
|
||||
|
||||
await interactionMenu.update({ components: [] });
|
||||
|
||||
if (interactionMenu.customId === `regenerate${interactionMenu.user.id}${interaction.id}`) {
|
||||
await interactionMenu.deferReply();
|
||||
await generate(interactionMenu, prompt, client);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -19,13 +19,13 @@ export default {
|
|||
const row = new ActionRowBuilder()
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`yes${interaction.user.id}`)
|
||||
.setCustomId(`yes${interaction.user.id}${interaction.id}`)
|
||||
.setLabel('Yes')
|
||||
.setStyle(ButtonStyle.Primary),
|
||||
)
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`no${interaction.user.id}`)
|
||||
.setCustomId(`no${interaction.user.id}${interaction.id}`)
|
||||
.setLabel('No')
|
||||
.setStyle(ButtonStyle.Danger),
|
||||
);
|
||||
|
@ -39,11 +39,18 @@ export default {
|
|||
return interaction.editReply({ content: 'Auto response has been enabled.', ephemeral: true });
|
||||
}
|
||||
|
||||
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}`) {
|
||||
return listenButton(client, interaction, interaction.user);
|
||||
},
|
||||
};
|
||||
|
||||
async function listenButton(client, interaction, user = interaction.user, originalId = interaction.id) {
|
||||
client.once('interactionCreate', async (interactionMenu) => {
|
||||
if (user !== interactionMenu.user) return listenButton(client, interaction, user, originalId);
|
||||
if (!interactionMenu.isButton()) return;
|
||||
|
||||
await interactionMenu.update({ components: [] });
|
||||
|
||||
if (interactionMenu.customId === `yes${interaction.user.id}${originalId}`) {
|
||||
const body = { serverID: interaction.guild.id, stat: 'disable' };
|
||||
await db.autoresponseStat.update(body, { where: { serverID: interaction.guild.id } });
|
||||
return interaction.editReply({ content: 'Auto response has been disabled.', ephemeral: true });
|
||||
|
@ -52,5 +59,4 @@ export default {
|
|||
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
|
@ -25,30 +25,37 @@ export default {
|
|||
const row = new ActionRowBuilder()
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`edit${interaction.user.id}`)
|
||||
.setCustomId(`edit${interaction.user.id}${interaction.id}`)
|
||||
.setLabel('Edit')
|
||||
.setStyle(ButtonStyle.Primary),
|
||||
)
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`remove${interaction.user.id}`)
|
||||
.setCustomId(`remove${interaction.user.id}${interaction.id}`)
|
||||
.setLabel('Remove')
|
||||
.setStyle(ButtonStyle.Danger),
|
||||
)
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`nothing${interaction.user.id}`)
|
||||
.setCustomId(`nothing${interaction.user.id}${interaction.id}`)
|
||||
.setLabel('Do nothing')
|
||||
.setStyle(ButtonStyle.Secondary),
|
||||
);
|
||||
|
||||
await interaction.reply({ content: 'The server already has a message set, do you want to edit it or remove it?', components: [row], ephemeral: true });
|
||||
|
||||
client.on('interactionCreate', async (interactionMenu) => {
|
||||
if (interaction.user !== interactionMenu.user) return;
|
||||
if (!interactionMenu.isButton) return;
|
||||
interactionMenu.update({ components: [] });
|
||||
if (interactionMenu.customId === `edit${interaction.user.id}`) {
|
||||
return listenButton(client, interaction, args, interaction.user);
|
||||
},
|
||||
};
|
||||
|
||||
async function listenButton(client, interaction, args, user = interaction.user, originalId = interaction.id) {
|
||||
client.once('interactionCreate', async (interactionMenu) => {
|
||||
if (user !== interactionMenu.user) return listenButton(client, interaction, args, user, originalId);
|
||||
if (!interactionMenu.isButton()) return;
|
||||
|
||||
await interactionMenu.update({ components: [] });
|
||||
|
||||
if (interactionMenu.customId === `edit${interaction.user.id}${originalId}`) {
|
||||
if (!args.message) {
|
||||
return interaction.reply({ content: 'You need to input a message for me to edit!', ephemeral: true });
|
||||
}
|
||||
|
@ -56,7 +63,7 @@ export default {
|
|||
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}`) {
|
||||
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 });
|
||||
}
|
||||
|
@ -64,5 +71,4 @@ export default {
|
|||
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
|
@ -20,13 +20,13 @@ export default {
|
|||
const row = new ActionRowBuilder()
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`yes${interaction.user.id}`)
|
||||
.setCustomId(`yes${interaction.user.id}${interaction.id}`)
|
||||
.setLabel('Yes')
|
||||
.setStyle(ButtonStyle.Primary),
|
||||
)
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`no${interaction.user.id}`)
|
||||
.setCustomId(`no${interaction.user.id}${interaction.id}`)
|
||||
.setLabel('No')
|
||||
.setStyle(ButtonStyle.Danger),
|
||||
);
|
||||
|
@ -40,18 +40,23 @@ export default {
|
|||
return interaction.editReply({ content: 'Quotation has been enabled.', ephemeral: true });
|
||||
}
|
||||
|
||||
client.on('interactionCreate', async (interactionMenu) => {
|
||||
if (interaction.user !== interactionMenu.user) return;
|
||||
if (!interactionMenu.isButton) return;
|
||||
return listenButton(client, interaction, interaction.user);
|
||||
},
|
||||
};
|
||||
|
||||
async function listenButton(client, interaction, user = interaction.user, originalId = interaction.id) {
|
||||
client.once('interactionCreate', async (interactionMenu) => {
|
||||
if (user !== interactionMenu.user) return listenButton(client, interaction, user, originalId);
|
||||
if (!interactionMenu.isButton()) return;
|
||||
|
||||
interactionMenu.update({ components: [] });
|
||||
if (interactionMenu.customId === `yes${interaction.user.id}`) {
|
||||
const body = { serverID: interaction.guild.id, stat: 'disable' };
|
||||
await db.quotationStat.update(body, { where: { serverID: interaction.guild.id } });
|
||||
|
||||
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 });
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
|
@ -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 fs from 'node:fs';
|
||||
|
||||
|
@ -51,12 +51,12 @@ export default {
|
|||
|
||||
if (args.remove) {
|
||||
if (tag) {
|
||||
if (tag.get('ownerID') == interaction.user.id || interaction.member.permissionsIn(interaction.channel).has('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 } });
|
||||
return interaction.editReply('successfully deleted the following tag: ' + args.trigger);
|
||||
}
|
||||
else {
|
||||
return interaction.editReply(`You are not the owner of this tag, if you think it is problematic ask 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 {
|
||||
|
@ -77,35 +77,46 @@ export default {
|
|||
const row = new ActionRowBuilder()
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`edit${interaction.user.id}`)
|
||||
.setCustomId(`edit${interaction.user.id}${interaction.id}`)
|
||||
.setLabel('Edit')
|
||||
.setStyle(ButtonStyle.Primary),
|
||||
)
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`remove${interaction.user.id}`)
|
||||
.setCustomId(`remove${interaction.user.id}${interaction.id}`)
|
||||
.setLabel('Remove')
|
||||
.setStyle(ButtonStyle.Danger),
|
||||
)
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`nothing${interaction.user.id}`)
|
||||
.setCustomId(`nothing${interaction.user.id}${interaction.id}`)
|
||||
.setLabel('Do nothing')
|
||||
.setStyle(ButtonStyle.Secondary),
|
||||
);
|
||||
|
||||
await interaction.editReply({ content: 'This tag already exist, do you want to update it, remove it or do nothing?', components: [row], ephemeral: true });
|
||||
|
||||
client.on('interactionCreate', async (interactionMenu) => {
|
||||
if (interaction.user !== interactionMenu.user) return;
|
||||
if (!interactionMenu.isButton) return;
|
||||
interactionMenu.update({ components: [] });
|
||||
if (interactionMenu.customId === `edit${interaction.user.id}`) {
|
||||
return listenButton(client, interaction, args, interaction.user);
|
||||
}
|
||||
else {
|
||||
return interaction.editReply(`You are not the owner of this tag, if you think it is problematic ask an admin to remove it by doing ${this.client.commandHandler.prefix[0]}tag ${args.trigger} --remove`);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
async function listenButton(client, interaction, args, user = interaction.user, originalId = interaction.id) {
|
||||
client.once('interactionCreate', async (interactionMenu) => {
|
||||
if (user !== interactionMenu.user) return listenButton(client, interaction, args, user, originalId);
|
||||
if (!interactionMenu.isButton()) return;
|
||||
|
||||
await interactionMenu.update({ components: [] });
|
||||
|
||||
if (interactionMenu.customId === `edit${interaction.user.id}${originalId}`) {
|
||||
const body = { trigger: args.trigger, response: args.response, ownerID: interaction.user.id, serverID: interaction.guild.id };
|
||||
await db.joinChannel.update(body, { where: { guildID: 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}`) {
|
||||
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 });
|
||||
}
|
||||
|
@ -113,20 +124,4 @@ export default {
|
|||
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
return interaction.editReply(`You are not the owner of this tag, if you think it is problematic ask an admin to remove it by doing ${this.client.commandHandler.prefix[0]}tag ${args.trigger} --remove`);
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
|
@ -26,30 +26,37 @@ export default {
|
|||
const row = new ActionRowBuilder()
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`edit${interaction.user.id}`)
|
||||
.setCustomId(`edit${interaction.user.id}${interaction.id}`)
|
||||
.setLabel('Edit')
|
||||
.setStyle(ButtonStyle.Primary),
|
||||
)
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`remove${interaction.user.id}`)
|
||||
.setCustomId(`remove${interaction.user.id}${interaction.id}`)
|
||||
.setLabel('Remove')
|
||||
.setStyle(ButtonStyle.Danger),
|
||||
)
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`nothing${interaction.user.id}`)
|
||||
.setCustomId(`nothing${interaction.user.id}${interaction.id}`)
|
||||
.setLabel('Do nothing')
|
||||
.setStyle(ButtonStyle.Secondary),
|
||||
);
|
||||
|
||||
await interaction.reply({ content: 'The server already has a message set, do you want to edit it or remove it?', components: [row], ephemeral: true });
|
||||
|
||||
client.on('interactionCreate', async (interactionMenu) => {
|
||||
if (interaction.user !== interactionMenu.user) return;
|
||||
if (!interactionMenu.isButton) return;
|
||||
interactionMenu.update({ components: [] });
|
||||
if (interactionMenu.customId === `edit${interaction.user.id}`) {
|
||||
return listenButton(client, interaction, args, interaction.user);
|
||||
},
|
||||
};
|
||||
|
||||
async function listenButton(client, interaction, args, user = interaction.user, originalId = interaction.id) {
|
||||
client.once('interactionCreate', async (interactionMenu) => {
|
||||
if (user !== interactionMenu.user) return listenButton(client, interaction, args, user, originalId);
|
||||
if (!interactionMenu.isButton()) return;
|
||||
|
||||
await interactionMenu.update({ components: [] });
|
||||
|
||||
if (interactionMenu.customId === `edit${interaction.user.id}${originalId}`) {
|
||||
if (!args.message) {
|
||||
return interaction.reply({ content: 'You need to input a message for me to edit!', ephemeral: true });
|
||||
}
|
||||
|
@ -57,7 +64,7 @@ export default {
|
|||
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}`) {
|
||||
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 });
|
||||
}
|
||||
|
@ -65,5 +72,4 @@ export default {
|
|||
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
|
@ -4,7 +4,7 @@ import TurndownService from 'turndown';
|
|||
const turndown = new TurndownService();
|
||||
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 {
|
||||
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 { EmbedBuilder } from 'discord.js';
|
||||
import Twit from 'twit';
|
||||
import { TwitterApi } from 'twitter-api-v2';
|
||||
import fetch from 'node-fetch';
|
||||
import os from 'node:os';
|
||||
import fs from 'node:fs';
|
||||
|
@ -8,7 +8,7 @@ import util from 'node:util';
|
|||
import stream from 'node:stream';
|
||||
|
||||
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 Blacklists = db.Blacklists;
|
||||
|
@ -16,10 +16,10 @@ const Blacklists = db.Blacklists;
|
|||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.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 =>
|
||||
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))
|
||||
.addAttachmentOption(option =>
|
||||
option.setName('image')
|
||||
|
@ -28,6 +28,7 @@ export default {
|
|||
category: 'fun',
|
||||
ratelimit: 3,
|
||||
cooldown: 86400,
|
||||
guildOnly: true,
|
||||
async execute(interaction, args, client) {
|
||||
const content = args.content;
|
||||
const attachment = args.image;
|
||||
|
@ -39,13 +40,31 @@ export default {
|
|||
await interaction.deferReply({ ephemeral: false });
|
||||
let tweet = content;
|
||||
const date = new Date();
|
||||
|
||||
// If guild is less than 1 month old don't accept the tweet
|
||||
if (interaction.guild.createdAt > date.setMonth(date.getMonth() - 1)) {
|
||||
await interaction.editReply({ content: 'The server need to be 1 month old to be able to use this command!' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset the date for the next check
|
||||
date.setTime(Date.now());
|
||||
|
||||
// If the bot has been in the guild for less than 1 week don't accept the tweet.
|
||||
if (interaction.guild.createdAt > date.setDate(date.getDate() - 7)) {
|
||||
await interaction.editReply({ content: 'I need to be in this server for a week to be able to use this command!' });
|
||||
}
|
||||
|
||||
// Reset the date for the next check
|
||||
date.setTime(Date.now());
|
||||
|
||||
// If account is less than 6 months old don't accept the tweet ( alt prevention )
|
||||
if (interaction.user.createdAt > date.setMonth(date.getMonth() - 6)) {
|
||||
await interaction.editReply({ content: 'Your account is too new to be able to use this command!' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset the current date so it checks correctly for the 1 year requirement.
|
||||
// Reset the date for the next check
|
||||
date.setTime(Date.now());
|
||||
|
||||
// If account is less than 1 year old don't accept attachment
|
||||
|
@ -54,13 +73,25 @@ export default {
|
|||
return;
|
||||
}
|
||||
|
||||
// remove zero width space
|
||||
if (tweet) {
|
||||
// remove zero width space
|
||||
tweet = tweet.replace('', '');
|
||||
// This should only happen if someone tweets a zero width space
|
||||
if (tweet.length === 0) {
|
||||
return interaction.reply({ content: 'Uh oh! You are missing any content for me to tweet!', ephemeral: true });
|
||||
}
|
||||
|
||||
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)
|
||||
/* No worky (I don't remember what the fuck I wrote here)
|
||||
if (wordToCensor.includes(tweet) || wordToCensor.includes(tweet.substring(0, tweet.length - 1)) || wordToCensor.includes(tweet.substring(1, tweet.length))) {
|
||||
const body = { type:'tweet', uid: interaction.user.id, reason: 'Automatic ban from banned word.' };
|
||||
Blacklists.create(body);
|
||||
|
@ -68,6 +99,7 @@ export default {
|
|||
await interaction.editReply({ content: 'Sike, you just posted cringe! Enjoy the blacklist :)' });
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
// Very simple link detection
|
||||
if (new RegExp('([a-zA-Z0-9]+://)?([a-zA-Z0-9_]+:[a-zA-Z0-9_]+@)?([a-zA-Z0-9.-]+\\.[A-Za-z]{2,4})(:[0-9]+)?(/.*)?').test(tweet) && !tweet.includes('twitter.com')) {
|
||||
|
@ -81,11 +113,12 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
const T = new Twit({
|
||||
consumer_key: twiConsumer,
|
||||
consumer_secret: twiConsumerSecret,
|
||||
access_token: twiToken,
|
||||
access_token_secret: twiTokenSecret,
|
||||
|
||||
const userClient = new TwitterApi({
|
||||
appKey: twiConsumer,
|
||||
appSecret: twiConsumerSecret,
|
||||
accessToken: twiToken,
|
||||
accessSecret: twiTokenSecret,
|
||||
});
|
||||
|
||||
try {
|
||||
|
@ -107,17 +140,8 @@ export default {
|
|||
return interaction.editReply({ content: 'Gifs can\'t be larger than 15 MB!' });
|
||||
}
|
||||
|
||||
const b64Image = fs.readFileSync(`${os.tmpdir()}/${attachment.name}`, { encoding: 'base64' });
|
||||
T.post('media/upload', { media_data: b64Image }, function(err, data) {
|
||||
if (err) {
|
||||
console.log('OH NO AN ERROR!!!!!!!');
|
||||
console.error(err);
|
||||
return interaction.editReply({ content: 'OH NO!!! AN ERROR HAS occurred!!! please hold on while i find what\'s causing this issue! ' });
|
||||
}
|
||||
else {
|
||||
Tweet(data);
|
||||
}
|
||||
});
|
||||
const image = await userClient.v1.uploadMedia(`${os.tmpdir()}/${attachment.name}`);
|
||||
Tweet(image);
|
||||
}
|
||||
else {
|
||||
await interaction.editReply({ content: 'File type not supported, you can only send jpg/png/gif' });
|
||||
|
@ -134,44 +158,18 @@ export default {
|
|||
return;
|
||||
}
|
||||
|
||||
function Tweet(data) {
|
||||
let options = {
|
||||
status: tweet,
|
||||
};
|
||||
|
||||
if (data && tweet) {
|
||||
options = {
|
||||
status: tweet,
|
||||
media_ids: new Array(data.media_id_string),
|
||||
};
|
||||
}
|
||||
else if (data) {
|
||||
options = {
|
||||
media_ids: new Array(data.media_id_string),
|
||||
};
|
||||
async function Tweet(img) {
|
||||
let options = null;
|
||||
if (img) {
|
||||
options = { media: { media_ids: new Array(img) } };
|
||||
}
|
||||
const tweeted = await userClient.v2.tweet(tweet, options);
|
||||
|
||||
T.post('statuses/update', options, function(err, response) {
|
||||
if (err) {
|
||||
// Rate limit exceeded
|
||||
if (err.code == 88) return interaction.editReply({ content: err.interaction });
|
||||
// Tweet needs to be a bit shorter.
|
||||
if (err.code == 186) return interaction.editReply({ content: `${err.interaction} Your interaction was ${tweet.length} characters, you need to remove ${tweet.length - 280} characters (This count may be inaccurate if your interaction contained link)` });
|
||||
// Status is a duplicate.
|
||||
if (err.code == 187) return interaction.editReply({ content: err.interaction });
|
||||
// To protect our users from spam and other malicious activity, this account is temporarily locked.
|
||||
if (err.code == 326) return interaction.editReply({ content: err.interaction });
|
||||
console.error('OH NO!!!!');
|
||||
console.error(err);
|
||||
return interaction.editReply({ content: 'OH NO!!! AN ERROR HAS occurred!!! please hold on while i find what\'s causing this issue!' });
|
||||
}
|
||||
|
||||
const tweetid = response.id_str;
|
||||
const tweetid = tweeted.data.id;
|
||||
const FunnyWords = ['oppaGangnamStyle', '69', '420', 'cum', 'funnyMan', 'GUCCISmartToilet', 'TwitterForClowns', 'fart', 'ok', 'hi', 'howAreYou', 'WhatsNinePlusTen', '21'];
|
||||
const TweetLink = `https://twitter.com/${FunnyWords[Math.floor((Math.random() * FunnyWords.length))]}/status/${tweetid}`;
|
||||
const TweetLink = `https://vxtwitter.com/${FunnyWords[Math.floor((Math.random() * FunnyWords.length))]}/status/${tweetid}`;
|
||||
|
||||
// Im too lazy for now to make an entry in config.json
|
||||
let channel = client.channels.resolve(twiChannel);
|
||||
let channel = await client.channels.resolve(twiChannel);
|
||||
channel.send(TweetLink);
|
||||
|
||||
const Embed = new EmbedBuilder()
|
||||
|
@ -198,10 +196,9 @@ export default {
|
|||
|
||||
if (attachment) Embed.setImage(attachment.url);
|
||||
|
||||
channel = client.channels.resolve(twiLogChannel);
|
||||
channel = await 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.')
|
||||
.setRequired(false)),
|
||||
category: 'fun',
|
||||
ratelimit: 2,
|
||||
cooldown: 60,
|
||||
parallelLimit: 30,
|
||||
async execute(interaction, args) {
|
||||
if (!interaction.channel.nsfw && !args.force) return interaction.reply(`Please execute this command in an NSFW channel ( Content might not be NSFW but since the video are user submitted better safe than sorry ) OR do \`\`${interaction.prefix}ytp --force\`\` to make the command work outside of nsfw channel BE AWARE THAT IT WON'T CHANGE THE FINAL RESULT SO NSFW CAN STILL HAPPEN`);
|
||||
|
||||
|
@ -69,19 +72,19 @@ export default {
|
|||
},
|
||||
};
|
||||
|
||||
new YTPGenerator().configurateAndGo(options)
|
||||
await new YTPGenerator().configurateAndGo(options)
|
||||
.then(() => {
|
||||
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 => {
|
||||
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 => {
|
||||
console.error(err);
|
||||
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 Twit from 'twit';
|
||||
|
|
@ -43,7 +43,7 @@ export default {
|
|||
.setTimestamp();
|
||||
|
||||
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();
|
||||
if (Attachment[0]) {
|
||||
|
@ -58,10 +58,10 @@ export default {
|
|||
else {
|
||||
client.users.resolve(user).send(Embed)
|
||||
.then(() => {
|
||||
return interaction.reply(`DM sent to ${user.tag}`);
|
||||
return interaction.reply(`DM sent to ${user.username}`);
|
||||
})
|
||||
.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,35 +32,53 @@ export default {
|
|||
if (!blacklist) {
|
||||
const body = { type:command, uid: userid, reason: reason };
|
||||
Blacklists.create(body);
|
||||
if (command === 'guild') {
|
||||
const guildid = userid;
|
||||
await client.guilds.fetch(guildid);
|
||||
const guild = client.guilds.resolve(guildid).name;
|
||||
|
||||
return interaction.editReply(`The guild ${guild} (${guildid}) has been blacklisted with the following reason \`${reason}\``);
|
||||
|
||||
}
|
||||
else {
|
||||
let user = userid;
|
||||
await client.users.fetch(userid);
|
||||
user = client.users.resolve(userid).tag;
|
||||
user = client.users.resolve(userid).username;
|
||||
|
||||
|
||||
return interaction.editReply(`${user} has been blacklisted from ${command} with the following reason \`${reason}\``);
|
||||
return interaction.editReply(`${user} (${userid}) has been blacklisted from ${command} with the following reason \`${reason}\``);
|
||||
}
|
||||
}
|
||||
else {
|
||||
const row = new ActionRowBuilder()
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`yes${interaction.user.id}`)
|
||||
.setCustomId(`yes${interaction.user.id}${interaction.id}`)
|
||||
.setLabel('Yes')
|
||||
.setStyle(ButtonStyle.Primary),
|
||||
)
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`no${interaction.user.id}`)
|
||||
.setCustomId(`no${interaction.user.id}${interaction.id}`)
|
||||
.setLabel('No')
|
||||
.setStyle(ButtonStyle.Danger),
|
||||
);
|
||||
|
||||
await interaction.editReply({ content: 'This user is already blacklisted, do you want to unblacklist him?', ephemeral: true, components: [row] });
|
||||
|
||||
interaction.client.once('interactionCreate', async (interactionMenu) => {
|
||||
if (interaction.user !== interactionMenu.user) return;
|
||||
if (!interactionMenu.isButton) return;
|
||||
interactionMenu.update({ components: [] });
|
||||
if (interactionMenu.customId === `yes${interaction.user.id}`) {
|
||||
return listenButton(client, interaction, command, userid, interaction.user);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
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}`);
|
||||
}
|
||||
|
@ -68,6 +86,4 @@ export default {
|
|||
return interaction.editReply('No one has been unblacklisted.');
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
|
@ -30,7 +30,7 @@ export default {
|
|||
data: ${JSON.stringify(client.commands.get(args.commandname).data)},
|
||||
category: '${client.commands.get(args.commandname).category}',
|
||||
async execute(interaction) {
|
||||
return interaction.reply('${args.placeholder}');
|
||||
return interaction.reply('${args.placeholder.replace(/'/g, '\\\'')}');
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { SlashCommandBuilder } 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';
|
||||
const donator = db.donator;
|
||||
|
||||
|
@ -15,7 +15,7 @@ export default {
|
|||
const Donator = await donator.findAll({ order: ['id'] });
|
||||
const client = interaction.client;
|
||||
const tina = await client.users.fetch('336492042299637771');
|
||||
const owner = await client.users.fetch('267065637183029248');
|
||||
const creator = await client.users.fetch('267065637183029248');
|
||||
const maintainer = await client.users.fetch(ownerId);
|
||||
|
||||
let description = 'I\'m a fun multipurpose bot made using [discord.js](https://github.com/discordjs/discord.js)'
|
||||
|
@ -25,7 +25,7 @@ export default {
|
|||
for (let i = 0; i < Donator.length; i++) {
|
||||
const user = await client.users.fetch(Donator[i].get('userID').toString());
|
||||
if (user !== null) {
|
||||
description += `**${user.tag} (${user.id}) | ${Donator[i].get('comment')}**\n`;
|
||||
description += `**${user.username} (${user.id}) | ${Donator[i].get('comment')}**\n`;
|
||||
}
|
||||
else {
|
||||
description += `**A user of discord (${user.id}) | ${Donator[i].get('comment')} (This user no longer share a server with the bot)**\n`;
|
||||
|
@ -36,26 +36,26 @@ export default {
|
|||
description += 'No one :(\n';
|
||||
}
|
||||
|
||||
description += `\nThanks to ${tina.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!';
|
||||
|
||||
exec('git rev-parse --short HEAD', (err, stdout) => {
|
||||
execFile('git', ['rev-parse', '--short', 'HEAD'], (err, stdout) => {
|
||||
const aboutEmbed = new EmbedBuilder()
|
||||
.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')
|
||||
.setDescription(description)
|
||||
.addFields(
|
||||
{ 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: 'Github (Mirror)', value: 'https://github.com/Supositware/Haha-yes', inline: true },
|
||||
{ name: 'Privacy Policy', value: 'https://libtar.de/discordprivacy.txt', inline: true },
|
||||
{ name: 'Status page', value: uptimePage.toString(), inline: true },
|
||||
|
||||
)
|
||||
.setFooter({ text: `Original bot made by ${owner.tag} (267065637183029248)` });
|
||||
.setFooter({ text: `Original bot made by ${creator.username} (267065637183029248)` });
|
||||
|
||||
interaction.reply({ embeds: [aboutEmbed] });
|
||||
});
|
||||
|
|
|
@ -18,6 +18,11 @@ export default {
|
|||
cooldown: 86400,
|
||||
async execute(interaction, args) {
|
||||
const url = args.url;
|
||||
// This is rather rudementary, a proper way would be using yt-dlp to know if it is a playlist
|
||||
if (url.includes('list=')) {
|
||||
return interaction.reply({ content: '❌ Playlists are not allowed!', ephemeral: true });
|
||||
}
|
||||
|
||||
if (!await utils.stringIsAValidurl(url)) {
|
||||
console.error(`Not a url!!! ${url}`);
|
||||
return interaction.reply({ content: '❌ This does not look like a valid url!', ephemeral: true });
|
||||
|
@ -25,7 +30,7 @@ export default {
|
|||
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
utils.downloadVideo(url, interaction.id, 'mp4')
|
||||
utils.downloadVideo(url, interaction.id, 'bestvideo[height<=?480]+bestaudio/best')
|
||||
.then(async () => {
|
||||
const file = fs.readdirSync(os.tmpdir()).filter(fn => fn.startsWith(interaction.id));
|
||||
const output = `${os.tmpdir()}/${file}`;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { SlashCommandBuilder } 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 {
|
||||
data: new SlashCommandBuilder()
|
||||
|
|
|
@ -17,7 +17,7 @@ export default {
|
|||
if (Donator[0]) {
|
||||
for (let i = 0; i < Donator.length; i++) {
|
||||
const user = await client.users.fetch(Donator[i].get('userID').toString());
|
||||
if (user !== null) {donatorMessage += `**${user.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`;}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
import { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, SelectMenuBuilder } from 'discord.js';
|
||||
import { exec } from 'node:child_process';
|
||||
import { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, StringSelectMenuBuilder } from 'discord.js';
|
||||
import { execFile } from 'node:child_process';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import utils from '../../utils/videos.js';
|
||||
|
||||
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 {
|
||||
data: new SlashCommandBuilder()
|
||||
|
@ -21,19 +26,27 @@ export default {
|
|||
.setRequired(false))
|
||||
.addBooleanOption(option =>
|
||||
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)),
|
||||
category: 'utility',
|
||||
alias: ['dl'],
|
||||
integration_types: [0, 1],
|
||||
|
||||
async execute(interaction, args, c) {
|
||||
client = c;
|
||||
const url = args.url;
|
||||
const format = args.format;
|
||||
maxFileSize = await utils.getMaxFileSize(interaction.guild);
|
||||
interaction.doCompress = args.compress;
|
||||
if (interaction.cleanUp) {
|
||||
cleanUp = interaction.cleanUp;
|
||||
}
|
||||
interaction.doAutocrop = args.autocrop;
|
||||
|
||||
await interaction.deferReply({ ephemeral: false });
|
||||
|
||||
|
@ -48,7 +61,12 @@ export default {
|
|||
|
||||
if (format) {
|
||||
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) {
|
||||
reject(stderr);
|
||||
}
|
||||
|
@ -58,8 +76,8 @@ export default {
|
|||
resolve(stdout);
|
||||
});
|
||||
});
|
||||
qualitys = JSON.parse(qualitys);
|
||||
|
||||
qualitys = JSON.parse(qualitys);
|
||||
const options = [];
|
||||
|
||||
qualitys.formats.forEach(f => {
|
||||
|
@ -89,8 +107,8 @@ export default {
|
|||
|
||||
const row = new ActionRowBuilder()
|
||||
.addComponents(
|
||||
new SelectMenuBuilder()
|
||||
.setCustomId(`downloadQuality${interaction.user.id}`)
|
||||
new StringSelectMenuBuilder()
|
||||
.setCustomId(`downloadQuality${interaction.user.id}${interaction.id}`)
|
||||
.setPlaceholder('Nothing selected')
|
||||
.setMinValues(1)
|
||||
.setMaxValues(2)
|
||||
|
@ -103,25 +121,37 @@ export default {
|
|||
client.on('interactionCreate', async (interactionMenu) => {
|
||||
if (interaction.user !== interactionMenu.user) 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 });
|
||||
download(url, interactionMenu, interaction);
|
||||
|
||||
await checkSize(url, interactionMenu.values[0], args, interaction);
|
||||
return download(url, interactionMenu, interaction, undefined, true);
|
||||
}
|
||||
});
|
||||
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) {
|
||||
let format = 'bestvideo*+bestaudio/best';
|
||||
async function download(url, interaction, originalInteraction, format = undefined, description = false) {
|
||||
let embedColour = 'Navy';
|
||||
if (interaction.member) {
|
||||
if (interaction.member.displayHexColor) {
|
||||
embedColour = interaction.member.displayHexColor;
|
||||
}
|
||||
}
|
||||
const Embed = new EmbedBuilder()
|
||||
.setColor(interaction.member ? interaction.member.displayHexColor : 'Navy')
|
||||
.setAuthor({ name: `Downloaded by ${interaction.user.tag}`, iconURL: interaction.user.displayAvatarURL(), url: url })
|
||||
.setFooter({ text: `You can get the original video by clicking on the "Downloaded by ${interaction.user.tag}" message!` });
|
||||
.setColor(embedColour)
|
||||
.setAuthor({ name: `Downloaded by ${interaction.user.username}`, iconURL: interaction.user.displayAvatarURL(), url: url })
|
||||
.setFooter({ text: `You can get the original video by clicking on the "Downloaded by ${interaction.user.username}" message!` });
|
||||
|
||||
if (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];
|
||||
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));
|
||||
let output = `${os.tmpdir()}/${file}`;
|
||||
|
||||
const fileStat = fs.statSync(output);
|
||||
const fileSize = fileStat.size / 1000000.0;
|
||||
const compressInteraction = originalInteraction ? originalInteraction : interaction;
|
||||
if (compressInteraction.doCompress) {
|
||||
const presets = [ 'Social 8 MB 3 Minutes 360p30', 'Social 50 MB 10 Minutes 480p30', 'Social 50 MB 5 Minutes 720p30', 'Social 100 MB 5 Minutes 1080p30' ];
|
||||
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 = [];
|
||||
|
||||
presets.forEach(p => {
|
||||
|
@ -147,8 +175,8 @@ async function download(url, interaction, originalInteraction) {
|
|||
|
||||
const row = new ActionRowBuilder()
|
||||
.addComponents(
|
||||
new SelectMenuBuilder()
|
||||
.setCustomId(`preset${interaction.user.id}`)
|
||||
new StringSelectMenuBuilder()
|
||||
.setCustomId(`preset${interaction.user.id}${interaction.id}`)
|
||||
.setPlaceholder('Nothing selected')
|
||||
.addOptions(options),
|
||||
);
|
||||
|
@ -158,42 +186,75 @@ async function download(url, interaction, originalInteraction) {
|
|||
client.on('interactionCreate', async (interactionMenu) => {
|
||||
if (interaction.user !== interactionMenu.user) 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 });
|
||||
compress(file, interactionMenu, Embed);
|
||||
if (interaction.isMessage) cleanUp();
|
||||
if (interaction.isMessage) {
|
||||
interaction.deleteReply();
|
||||
interaction.cleanUp();
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// If the video format is not one compatible with Discord, reencode it.
|
||||
const bannedFormats = ['hevc'];
|
||||
// If the video format is not one compatible with Discord, reencode it unless autocrop is choosen in which case it gets reencoded anyway.
|
||||
if (!interaction.doAutocrop) {
|
||||
const bannedFormats = ['av1'];
|
||||
const codec = await utils.getVideoCodec(output);
|
||||
|
||||
if (bannedFormats.includes(codec)) {
|
||||
console.log('Reencoding video');
|
||||
const oldOutput = output;
|
||||
output = `${os.tmpdir()}/264${file}`;
|
||||
await utils.ffmpeg(`-i ${oldOutput} -vcodec libx264 -acodec aac ${output}`);
|
||||
await utils.ffmpeg(['-i', oldOutput, '-vcodec', 'libx264', '-acodec', 'aac', output]);
|
||||
}
|
||||
}
|
||||
else if (interaction.doAutocrop && !compressInteraction.doCompress) {
|
||||
const oldOutput = output;
|
||||
output = `${os.tmpdir()}/autocrop${file}`;
|
||||
await utils.autoCrop(oldOutput, output);
|
||||
}
|
||||
|
||||
const fileStat = fs.statSync(output);
|
||||
const fileSize = fileStat.size / 1000000.0;
|
||||
|
||||
Embed.setAuthor({ name: `${Embed.data.author.name} (${fileSize.toFixed(2)} MB)`, iconURL: Embed.data.author.icon_url, url: Embed.data.author.url });
|
||||
|
||||
let message = null;
|
||||
if (interaction.isMessage && interaction.reference !== null) {
|
||||
const channel = client.channels.resolve(interaction.reference.channelId);
|
||||
message = await channel.messages.fetch(interaction.reference.messageId);
|
||||
}
|
||||
|
||||
if (fileSize > 100) {
|
||||
await interaction.deleteReply();
|
||||
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)
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
await interaction.editReply({ content: 'File was bigger than 8 mb. It has been uploaded to an external site.', embeds: [Embed], ephemeral: false });
|
||||
|
||||
await interaction.editReply({ content: `File was bigger than ${maxFileSize} mb. It has been uploaded to an external site.`, embeds: [Embed], ephemeral: false });
|
||||
if (interaction.isMessage && message) {
|
||||
await message.reply({ content: fileurl });
|
||||
}
|
||||
else {
|
||||
await interaction.followUp({ content: fileurl, ephemeral: false });
|
||||
}
|
||||
}
|
||||
else if (interaction.isMessage && message) {
|
||||
await message.reply({ embeds: [Embed], files: [output] });
|
||||
}
|
||||
else {
|
||||
await interaction.editReply({ embeds: [Embed], files: [output], ephemeral: false });
|
||||
}
|
||||
if (interaction.isMessage) cleanUp();
|
||||
|
||||
if (interaction.isMessage) {
|
||||
interaction.deleteReply();
|
||||
interaction.cleanUp();
|
||||
}
|
||||
})
|
||||
.catch(async err => {
|
||||
console.error(err);
|
||||
|
@ -206,17 +267,68 @@ async function download(url, interaction, originalInteraction) {
|
|||
async function compress(input, interaction, embed) {
|
||||
const output = `compressed${input}.mp4`;
|
||||
// Delete the file as it apparently don't overwrite?
|
||||
if (fs.existsSync(output)) {
|
||||
fs.rmSync(output);
|
||||
}
|
||||
|
||||
utils.compressVideo(`${os.tmpdir()}/${input}`, output, interaction.values[0])
|
||||
.then(async () => {
|
||||
const fileStat = fs.statSync(`${os.tmpdir()}/${output}`);
|
||||
const fileSize = fileStat.size / 1000000.0;
|
||||
|
||||
if (fileSize > 8) {
|
||||
await interaction.editReply({ content: 'File was bigger than 8 mb. but due to the compression it is not being uploaded externally.', ephemeral: true });
|
||||
embed.setAuthor({ name: `${embed.data.author.name} (${fileSize.toFixed(2)} MB)`, iconURL: embed.data.author.icon_url, url: embed.data.author.url });
|
||||
|
||||
if (fileSize > maxFileSize) {
|
||||
await interaction.editReply({ content: `File was bigger than ${maxFileSize} mb. It has been uploaded to an external site.`, ephemeral: false });
|
||||
}
|
||||
else {
|
||||
await interaction.editReply({ embeds: [embed], files: [`${os.tmpdir()}/${output}`], ephemeral: false });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function checkSize(url, format, args, interaction, tries = 0) {
|
||||
const resolutions = [144, 240, 360, 480, 720, 1080, 1440, 2160];
|
||||
|
||||
while (tries < 4) {
|
||||
format = `bestvideo[height<=?${resolutions[resolutions.indexOf(ytdlpMaxResolution) - tries]}]+bestaudio/best`;
|
||||
const aproxFileSize = await utils.getVideoSize(url, format);
|
||||
if (isNaN(aproxFileSize)) return format;
|
||||
|
||||
if (format || tries >= 4) {
|
||||
if (aproxFileSize > 100 && !args.compress && tries > 4) {
|
||||
return await interaction.followUp(`Uh oh! The video you tried to download is larger than 100 mb (is ${aproxFileSize} mb)! Try again with a lower resolution format.`);
|
||||
}
|
||||
else if (aproxFileSize > 500 && tries > 4) {
|
||||
return await interaction.followUp(`Uh oh! The video you tried to download is larger than 500 mb (is ${aproxFileSize} mb)! Try again with a lower resolution format.`);
|
||||
}
|
||||
}
|
||||
|
||||
if (aproxFileSize < 100) {
|
||||
return format;
|
||||
}
|
||||
|
||||
if (tries < 4 && aproxFileSize > 100) {
|
||||
tries++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getVideoDescription(urlArg) {
|
||||
return await new Promise((resolve, reject) => {
|
||||
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',
|
||||
async execute(interaction, args) {
|
||||
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();
|
||||
|
||||
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 fs from 'node:fs';
|
||||
import ratelimiter from '../../utils/ratelimiter.js';
|
||||
|
||||
const { ownerId, prefix } = process.env;
|
||||
const prefixs = prefix.split(',');
|
||||
|
@ -113,6 +114,13 @@ export default {
|
|||
embed.addFields({ name: 'Bot permission', value: `\`${perm.join('` `')}\``, inline: true });
|
||||
}
|
||||
|
||||
if (command.parallelLimit) {
|
||||
const paralellimit = ratelimiter.checkParallel(interaction.user, command.data.name, command);
|
||||
|
||||
embed.addFields({ name: 'Current number of executions', value: `\`${paralellimit.current}\``, inline: false });
|
||||
embed.addFields({ name: 'Maximum number of executions', value: `\`${command.parallelLimit}\``, inline: true });
|
||||
}
|
||||
|
||||
if (fs.existsSync(`./asset/img/command/${command.category}/${command.data.name}.png`)) {
|
||||
const file = new AttachmentBuilder(`./asset/img/command/${command.category}/${command.data.name}.png`);
|
||||
embed.setImage(`attachment://${command.data.name}.png`);
|
||||
|
|
|
@ -8,6 +8,7 @@ export default {
|
|||
.setDescription('The bot you want to make an invite link for.')
|
||||
.setRequired(false)),
|
||||
category: 'utility',
|
||||
integration_types: [0, 1],
|
||||
async execute(interaction, args, client) {
|
||||
if (args.bot) {
|
||||
if (args.bot.user.bot) {
|
||||
|
@ -18,7 +19,9 @@ export default {
|
|||
}
|
||||
}
|
||||
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')
|
||||
.setDescription('Opt out of the non commands features and arguments logging (for debugging purposes)'),
|
||||
category: 'utility',
|
||||
integration_types: [0, 1],
|
||||
|
||||
async execute(interaction, args, client) {
|
||||
const isOptOut = await db.optout.findOne({ where: { userID: interaction.user.id } });
|
||||
|
||||
if (!isOptOut) {
|
||||
const body = { userID: interaction.user.id };
|
||||
await db.optout.create(body);
|
||||
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}`)
|
||||
.setCustomId(`yes${interaction.user.id}${interaction.id}`)
|
||||
.setLabel('Yes')
|
||||
.setStyle(ButtonStyle.Primary),
|
||||
)
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(`no${interaction.user.id}`)
|
||||
.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] });
|
||||
await interaction.reply({ content: 'You are already opt out, do you wish to opt in?', components: [row], ephemeral: true });
|
||||
|
||||
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');
|
||||
listenButton(client, interaction, interaction.user);
|
||||
}
|
||||
else {
|
||||
return interaction.editReply('Nothing has been changed.');
|
||||
}
|
||||
});
|
||||
|
||||
return interaction.followUp({
|
||||
content:
|
||||
'As a reminder here what opting out does:\n'
|
||||
+ '- Your user ID will no longer be used for debug logging.\n'
|
||||
+ '- servers will no longer be shown in added/kicked stats.\n'
|
||||
+ '- Your messages won\'t be quoted.\n'
|
||||
+ '- Won\'t show the arguments from commands.',
|
||||
ephemeral: true,
|
||||
},
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
async function listenButton(client, interaction, user = interaction.user, originalId = interaction.id) {
|
||||
client.once('interactionCreate', async (interactionMenu) => {
|
||||
if (user !== interactionMenu.user) return listenButton(client, interaction, user, originalId);
|
||||
if (!interactionMenu.isButton()) return;
|
||||
|
||||
await interactionMenu.update({ components: [] });
|
||||
|
||||
if (interactionMenu.customId === `yes${interaction.user.id}${originalId}`) {
|
||||
db.optout.destroy({ where: { userID: interaction.user.id } });
|
||||
return interaction.editReply({ content: 'You have successfully been opt in', ephemeral: true });
|
||||
}
|
||||
else {
|
||||
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
|
||||
}
|
||||
});
|
||||
}
|
|
@ -5,8 +5,9 @@ export default {
|
|||
.setName('ping')
|
||||
.setDescription('Replies with Pong!'),
|
||||
category: 'utility',
|
||||
async execute(interaction) {
|
||||
integration_types: [0, 1],
|
||||
|
||||
async execute(interaction) {
|
||||
const row = new ActionRowBuilder()
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
|
|
|
@ -36,13 +36,13 @@ export default {
|
|||
const statsEmbed = new EmbedBuilder()
|
||||
.setColor(interaction.member ? interaction.member.displayHexColor : 'Navy')
|
||||
.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(
|
||||
{ name: 'Servers', value: client.guilds.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: '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: 'Nodejs version', value: process.version, inline: true },
|
||||
{ name: 'Discord.js version', value: version, inline: true },
|
||||
|
|
|
@ -20,7 +20,7 @@ export default {
|
|||
}
|
||||
const Embed = new EmbedBuilder()
|
||||
.setColor(member ? member.displayHexColor : 'Navy')
|
||||
.setAuthor({ name: `${user.tag} (${user.id})`, iconURL: user.displayAvatarURL() })
|
||||
.setAuthor({ name: `${user.username} (${user.id})`, iconURL: user.displayAvatarURL() })
|
||||
.addFields(
|
||||
{ name: 'Current rank hex color', value: member ? member.displayHexColor : 'No rank color', inline: true },
|
||||
{ name: 'Joined guild at', value: member ? member.joinedAt.toString() : 'Not in this guild', inline: true },
|
||||
|
|
|
@ -3,9 +3,9 @@ import utils from '../../utils/videos.js';
|
|||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { exec } from 'node:child_process';
|
||||
import { execFile } from 'node:child_process';
|
||||
const { NODE_ENV } = process.env;
|
||||
|
||||
const ytdlpFormat = 'bestvideo[height<=?480]/best';
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
|
@ -14,44 +14,101 @@ export default {
|
|||
.addStringOption(option =>
|
||||
option.setName('url')
|
||||
.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',
|
||||
alias: ['v2g', 'togif'],
|
||||
integration_types: [0, 1],
|
||||
|
||||
async execute(interaction, args) {
|
||||
await interaction.deferReply({ ephemeral: false });
|
||||
const maxFileSize = await utils.getMaxFileSize(interaction.guild);
|
||||
const url = args.url;
|
||||
let quality = args.quality;
|
||||
if (quality) {
|
||||
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)) {
|
||||
console.error(`Not a url!!! ${url}`);
|
||||
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 () => {
|
||||
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 gifsicleOutput = output.replace(path.extname(output), 'gifsicle.gif');
|
||||
|
||||
// Extract every frame for gifski
|
||||
await utils.ffmpeg(`-i ${output} ${os.tmpdir()}/frame${interaction.id}%04d.png`);
|
||||
await utils.ffmpeg(['-i', output, `${os.tmpdir()}/frame${interaction.id}%04d.png`]);
|
||||
// Make it look better
|
||||
await gifski(gifskiOutput, `${os.tmpdir()}/frame${interaction.id}*`);
|
||||
await gifski(gifskiOutput, `${os.tmpdir()}/frame${interaction.id}*`, quality, await args.fps);
|
||||
// Optimize it
|
||||
await gifsicle(gifskiOutput, gifsicleOutput);
|
||||
await gifsicle(gifskiOutput, gifsicleOutput, args.noloop);
|
||||
|
||||
const fileStat = fs.statSync(gifsicleOutput);
|
||||
const fileSize = fileStat.size / 1000000.0;
|
||||
|
||||
if (fileSize > 100) {
|
||||
if (fileSize > 25) {
|
||||
await interaction.deleteReply();
|
||||
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)
|
||||
.catch(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 {
|
||||
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) => {
|
||||
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) {
|
||||
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) => {
|
||||
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) {
|
||||
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 {
|
||||
name: 'guildDelete',
|
||||
async execute(guild, client) {
|
||||
if (!guild.available) return;
|
||||
const guildOwner = await client.users.fetch(guild.ownerId);
|
||||
|
||||
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 db from '../../models/index.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 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) {
|
||||
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 });
|
||||
}
|
||||
|
||||
const userTag = interaction.user.tag;
|
||||
const userTag = interaction.user.username;
|
||||
const userID = interaction.user.id;
|
||||
const commandName = interaction.commandName;
|
||||
|
||||
|
@ -27,21 +38,26 @@ export default {
|
|||
|
||||
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) {
|
||||
console.log(`A user launched command \x1b[33m${commandName}\x1b[0m with slash`);
|
||||
}
|
||||
else {
|
||||
console.log(`\x1b[33m${userTag} (${userID})\x1b[0m launched command \x1b[33m${commandName}\x1b[0m with slash`);
|
||||
if (commandName === 'optout') {
|
||||
isOptOut = true;
|
||||
}
|
||||
|
||||
const timestamp = new Date();
|
||||
console.log(`[${timestamp.toISOString()}] \x1b[33m${ isOptOut ? 'A user' : `${userTag} (${userID})`}\x1b[0m launched command \x1b[33m${commandName}\x1b[0m using slash`);
|
||||
|
||||
|
||||
// Owner only check
|
||||
if (command.ownerOnly && interaction.user.id !== ownerId) {
|
||||
return interaction.reply({ content: '❌ This command is reserved for the owner!', ephemeral: true });
|
||||
}
|
||||
|
||||
// Guild only check
|
||||
if (command.guildOnly && !interaction.guild) {
|
||||
return interaction.reply({ content: '❌ This command only work in a server!', ephemeral: true });
|
||||
}
|
||||
|
||||
// Check if the bot has the needed permissions
|
||||
if (command.default_permission) {
|
||||
const clientMember = await interaction.guild.members.fetch(client.user.id);
|
||||
|
@ -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
|
||||
const doRateLimit = ratelimiter.check(interaction.user, commandName, command);
|
||||
const doRateLimit = await ratelimiter.check(interaction.user, commandName, command);
|
||||
if (doRateLimit) {
|
||||
return interaction.reply({ content: doRateLimit, ephemeral: true });
|
||||
|
||||
|
@ -83,14 +109,20 @@ export default {
|
|||
});
|
||||
|
||||
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) {
|
||||
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
|
||||
// TODO: Moving that to a dedicated function that works for both messages and interactions
|
||||
|
||||
let hasPrefix = false;
|
||||
prefixs.forEach(p => {
|
||||
|
@ -283,6 +284,14 @@ export default {
|
|||
const globalBlacklist = await db.Blacklists.findOne({ where: { type:'global', uid:message.author.id } });
|
||||
const commandBlacklist = await db.Blacklists.findOne({ where: { type:commandName, uid:message.author.id } });
|
||||
|
||||
if (message.guild) {
|
||||
const serverBlacklist = await db.Blacklists.findOne({ where: { type:'guild', uid:message.guild.id } });
|
||||
if (serverBlacklist) {
|
||||
message.reply({ content: `This guild has been blacklisted for the following reason: \`${serverBlacklist.reason}\``, ephemeral: true });
|
||||
return message.guild.leave();
|
||||
}
|
||||
}
|
||||
|
||||
if (globalBlacklist) {
|
||||
return message.reply({ content: `You are globally blacklisted for the following reason: \`${globalBlacklist.reason}\``, ephemeral: true });
|
||||
}
|
||||
|
@ -290,23 +299,29 @@ export default {
|
|||
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 isOptOut = await db.optout.findOne({ where: { userID: message.author.id } });
|
||||
let isOptOut = await db.optout.findOne({ where: { userID: message.author.id } });
|
||||
|
||||
if (isOptOut) {
|
||||
console.log(`A user launched command \x1b[33m${commandName}\x1b[0m with prefix`);
|
||||
}
|
||||
else {
|
||||
console.log(`\x1b[33m${userTag} (${userID})\x1b[0m launched command \x1b[33m${commandName}\x1b[0m with prefix`);
|
||||
if (commandName === 'optout') {
|
||||
isOptOut = true;
|
||||
}
|
||||
|
||||
const timestamp = new Date();
|
||||
console.log(`[${timestamp.toISOString()}] \x1b[33m${ isOptOut ? 'A user' : `${userTag} (${userID})`}\x1b[0m launched command \x1b[33m${commandName}\x1b[0m using prefix`);
|
||||
|
||||
|
||||
// Owner only check
|
||||
if (command.ownerOnly && message.author.id !== ownerId) {
|
||||
return message.reply({ content: '❌ This command is reserved for the owner!', ephemeral: true });
|
||||
}
|
||||
|
||||
// Guild only check
|
||||
if (command.guildOnly && !message.guild) {
|
||||
return message.reply({ content: '❌ This command only work in a server!', ephemeral: true });
|
||||
}
|
||||
|
||||
// Check if the bot has the needed permissions
|
||||
if (command.clientPermissions) {
|
||||
const clientMember = await message.guild.members.fetch(client.user.id);
|
||||
|
@ -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
|
||||
const doRateLimit = ratelimiter.check(message.author, commandName, command);
|
||||
const doRateLimit = await ratelimiter.check(message.author, commandName, command);
|
||||
if (doRateLimit) {
|
||||
return message.reply({ content: doRateLimit, ephemeral: true });
|
||||
|
||||
|
@ -377,11 +402,17 @@ export default {
|
|||
});
|
||||
|
||||
const argsLength = command.data.options.length - argsToDelete;
|
||||
const missingRequired = [];
|
||||
|
||||
for (let i = 0, j = 0; i < argsLength; i++, j++) {
|
||||
if (!messageArgs[i]) continue;
|
||||
const arg = command.data.options[j];
|
||||
|
||||
if (arg.required && !messageArgs[i]) {
|
||||
missingRequired.push({ name: arg.name, description: arg.description });
|
||||
}
|
||||
|
||||
if (!messageArgs[i]) continue;
|
||||
|
||||
if (arg.type === ApplicationCommandOptionType.Attachment) continue;
|
||||
|
||||
let payloadName = arg.name;
|
||||
|
@ -391,13 +422,16 @@ export default {
|
|||
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);
|
||||
payload = true;
|
||||
j--;
|
||||
}
|
||||
|
||||
if (arg.type === ApplicationCommandOptionType.Mentionable) {
|
||||
if (arg.type === ApplicationCommandOptionType.Mentionable || arg.type === ApplicationCommandOptionType.User) {
|
||||
await message.guild.members.fetch();
|
||||
payload = message.mentions.members.first() ? message.mentions.members.first() : message.guild.members.cache.find(u => u.user.username.toLowerCase().includes(payload.toLowerCase()));
|
||||
}
|
||||
|
@ -405,15 +439,33 @@ export default {
|
|||
args[payloadName] = payload;
|
||||
}
|
||||
|
||||
if (!isOptOut) {
|
||||
console.log(`\x1b[33m${commandName}\x1b[0m with args ${JSON.stringify(args)}`);
|
||||
if (!isOptOut && argsLength > 0) {
|
||||
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) {
|
||||
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}` })
|
||||
.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;
|
||||
|
||||
|
|
|
@ -111,7 +111,7 @@ export default {
|
|||
.setFooter({ text: `${emote} ${reactionCount}` })
|
||||
.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] });
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { exec } from 'node:child_process';
|
||||
import { execFile } from 'node:child_process';
|
||||
const { statusChannel, NODE_ENV } = process.env;
|
||||
import { version } from 'discord.js';
|
||||
|
||||
export default {
|
||||
name: 'ready',
|
||||
|
@ -8,8 +9,21 @@ export default {
|
|||
// Init global variables.
|
||||
global.boards = {};
|
||||
|
||||
const commandSize = client.commands.size;
|
||||
const clientTag = client.user.username;
|
||||
const guildSize = client.guilds.cache.size;
|
||||
const channelSize = client.channels.cache.size;
|
||||
const clientID = client.user.id;
|
||||
|
||||
console.log('===========[ READY ]===========');
|
||||
console.log(`\x1b[32mLogged in as \x1b[34m${clientTag}\x1b[0m! (\x1b[33m${clientID}\x1b[0m)`);
|
||||
console.log(`Ready to serve in \x1b[33m${channelSize}\x1b[0m channels on \x1b[33m${guildSize}\x1b[0m servers.`);
|
||||
console.log(`${client.readyAt}`);
|
||||
console.log(`There is \x1b[33m${commandSize}\x1b[0m command loaded.`);
|
||||
console.log(`Running Discord.js \x1b[33m${version}\x1b[0m`);
|
||||
|
||||
const ytdlpVersion = await new Promise((resolve, reject) => {
|
||||
exec('./bin/yt-dlp --version', (err, stdout, stderr) => {
|
||||
execFile('./bin/yt-dlp', ['--version'], (err, stdout, stderr) => {
|
||||
if (err) {
|
||||
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('===========[ READY ]===========');
|
||||
|
||||
// If stats channel settings exist, send bot stats to it
|
||||
if (statusChannel && NODE_ENV !== 'development') {
|
||||
const channel = client.channels.resolve(statusChannel);
|
||||
channel.send(`Ready to serve in ${channelSize} channels on ${guildSize} servers.\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 watch from '../../json/watching.json' assert {type: 'json'};
|
||||
import { ActivityType } from 'discord.js';
|
||||
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 {
|
||||
name: 'ready',
|
||||
once: true,
|
||||
async execute(client) {
|
||||
// Bot status
|
||||
setStatus();
|
||||
await setStatus();
|
||||
// Change status every 30 minutes
|
||||
setInterval(async () => {
|
||||
setStatus();
|
||||
await setStatus();
|
||||
}, 1800000);
|
||||
|
||||
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
|
||||
if (random === 0) {
|
||||
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!';
|
||||
console.log(`Setting status to: ${status}`);
|
||||
client.user.setActivity(status, { type: 'WATCHING' });
|
||||
types = [ ActivityType.Watching ];
|
||||
}
|
||||
// Random "Playing" status taken from json
|
||||
else if (random === 1) {
|
||||
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!';
|
||||
|
||||
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 path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
import { Client, Collection, GatewayIntentBits, Partials } from 'discord.js';
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
const { token, NODE_ENV } = process.env;
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
|
@ -41,7 +39,7 @@ async function loadCommandFromDir(dir) {
|
|||
|
||||
for (const file of commandFiles) {
|
||||
const filePath = path.join(commandsPath, file);
|
||||
import(filePath)
|
||||
import(pathToFileURL(filePath))
|
||||
.then(importedCommand => {
|
||||
const command = importedCommand.default;
|
||||
client.commands.set(command.data.name, command);
|
||||
|
@ -57,7 +55,7 @@ async function loadEventFromDir(dir, listener) {
|
|||
|
||||
for (const file of eventFiles) {
|
||||
const filePath = path.join(eventsPath, file);
|
||||
import(filePath)
|
||||
import(pathToFileURL(filePath))
|
||||
.then(importedEvent => {
|
||||
const event = importedEvent.default;
|
||||
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": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node .",
|
||||
"deploy": "node deploy-commands.cjs",
|
||||
"deployGlobally": "node deploy-commands.cjs global",
|
||||
"start": "node --env-file .env .",
|
||||
"deploy": "node --env-file .env scripts/deploy-commands.js",
|
||||
"deployGlobally": "node --env-file .env scripts/deploy-commands.js global",
|
||||
"lint": "eslint .",
|
||||
"lintfix": "eslint . --fix",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
|
@ -17,25 +17,25 @@
|
|||
"homepage": "https://libtar.de",
|
||||
"license": "AGPL",
|
||||
"dependencies": {
|
||||
"@discordjs/rest": "^0.4.1",
|
||||
"discord-api-types": "^0.33.5",
|
||||
"discord.js": "^14.9.0",
|
||||
"dotenv": "^16.0.1",
|
||||
"mariadb": "^3.0.1",
|
||||
"mysql2": "^2.3.3",
|
||||
"node-fetch": "^3.2.6",
|
||||
"safe-regex": "github:davisjam/safe-regex",
|
||||
"sequelize": "^6.21.3",
|
||||
"turndown": "^7.1.1",
|
||||
"twit": "^1.1.20",
|
||||
"@discordjs/rest": "^2.3.0",
|
||||
"discord-api-types": "^0.37.91",
|
||||
"discord.js": "^14.15.3",
|
||||
"mariadb": "^3.3.1",
|
||||
"node-fetch": "^3.3.2",
|
||||
"sequelize": "^6.37.3",
|
||||
"turndown": "^7.2.0",
|
||||
"twitter-api-v2": "^1.17.1",
|
||||
"ytpplus-node": "github:Supositware/ytpplus-node"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "^7.18.9",
|
||||
"@babel/plugin-syntax-import-assertions": "^7.18.6",
|
||||
"@eslint/eslintrc": "^3.1.0",
|
||||
"@eslint/js": "^9.6.0",
|
||||
"@types/node": "^18.7.3",
|
||||
"eslint": "^8.16.0",
|
||||
"eslint": "^8.57.0",
|
||||
"globals": "^15.8.0",
|
||||
"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
|
||||
|
||||
|
||||
* 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))
|
||||
* HandBrakeCLI (For [download](commands/utility/download.js))
|
||||
* gifsicle (For [vid2gif](commands/utility/vid2gif.js))
|
||||
* gifki (For [vid2gif](commands/utility/vid2gif.js))
|
||||
* Somewhere to upload files larger than 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
|
||||
```
|
||||
|
@ -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.
|
||||
``node scripts/deploy-commands.cjs``
|
||||
``node --env-file .env scripts/deploy-commands.cjs``
|
||||
|
||||
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
|
||||
```
|
||||
|
@ -38,7 +38,7 @@ npm install -g pm2
|
|||
pm2 start index.js --name (insert name)
|
||||
```
|
||||
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
|
||||
|
||||
|
|
|
@ -2,9 +2,7 @@ import { REST } from '@discordjs/rest';
|
|||
import { Routes } from 'discord-api-types/v9';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
const { clientId, guildId, token } = process.env;
|
||||
|
||||
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'));
|
||||
for (const file of commandFiles) {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,11 +23,11 @@ async function download(url, output) {
|
|||
fs.renameSync(tmpPath, path);
|
||||
fs.chmodSync(path, '755');
|
||||
console.log(`${url} download finished.`);
|
||||
resolve(true);
|
||||
return resolve(true);
|
||||
});
|
||||
filePath.on('error', (err) => {
|
||||
filePath.close();
|
||||
reject(err);
|
||||
return reject(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
import dotenv from 'dotenv';
|
||||
import fetch from 'node-fetch';
|
||||
import { Client, GatewayIntentBits } from 'discord.js';
|
||||
|
||||
|
||||
dotenv.config();
|
||||
const { botsggToken, botsggEndpoint, token } = process.env;
|
||||
|
||||
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';
|
||||
|
||||
if (process.platform !== 'linux' && process.argv[2] !== '-f') {
|
||||
console.error('This script only download the linux version of yt-dlp. If you want to download anyway try again with -f');
|
||||
(async () => {
|
||||
if (process.platform !== 'linux' && process.argv[2] !== '-f') {
|
||||
console.error('This script only download the linux version of yt-dlp. If you want to download anyway try again with -f or execute ./bin/yt-dlp --update-to nightly');
|
||||
process.exit(1);
|
||||
}
|
||||
else if (process.platform !== 'linux' && process.argv[2] === '-f') {
|
||||
}
|
||||
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);
|
||||
|
||||
for (const match of matches) {
|
||||
if (search(match[0].trim(), variables)) { text = text.replace(match[0].trim(), search(match[0].trim(), variables).value); }
|
||||
if (search(match[0].trim(), variables)) {
|
||||
text = text.replace(match[0].trim(), search(match[0].trim(), variables).value);
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
|
|
|
@ -1,12 +1,25 @@
|
|||
const ratelimit = {};
|
||||
const parallelLimit = {};
|
||||
const { ownerId, NODE_ENV } = process.env;
|
||||
|
||||
import db from '../models/index.js';
|
||||
|
||||
export default {
|
||||
check,
|
||||
addParallel,
|
||||
removeParallel,
|
||||
checkParallel,
|
||||
};
|
||||
function check(user, commandName, commands) {
|
||||
async function check(user, commandName, commands) {
|
||||
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]) {
|
||||
ratelimit[userID] = {};
|
||||
|
@ -27,13 +40,11 @@ function check(user, commandName, commands) {
|
|||
const hours = Math.floor(minutes / 60);
|
||||
const dateString = `${hours > 0 ? ` ${Math.floor(hours)} hours` : ''}${minutes > 0 ? ` ${Math.floor(minutes % 60)} minutes` : ''}${seconds > 0 ? ` ${Math.floor(seconds % 60)} seconds` : ''}`;
|
||||
|
||||
const isOptOut = db.optout.findOne({ where: { userID: userID } });
|
||||
if (isOptOut) {
|
||||
console.log(`A user is rate limited on \x1b[33m${commandName}\x1b[0m for${dateString}.`);
|
||||
}
|
||||
else {
|
||||
console.log(`\x1b[33m${userTag} (${userID})\x1b[0m is rate limited on \x1b[33m${commandName}\x1b[0m for${dateString}.`);
|
||||
}
|
||||
const isOptOut = await db.optout.findOne({ where: { userID: userID } });
|
||||
|
||||
const timestamp = new Date();
|
||||
console.log(`[${timestamp.toISOString()}] \x1b[33m${ isOptOut ? 'A user' : `${userTag} (${userID})`}\x1b[0m is rate limited on \x1b[33m${commandName}\x1b[0m for${dateString}.`);
|
||||
|
||||
return `You are being rate limited. You can try again in${dateString}.`;
|
||||
}
|
||||
}
|
||||
|
@ -46,3 +57,47 @@ function check(user, commandName, commands) {
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
async function addParallel(commandName) {
|
||||
// console.log(`[ADD] Adding parallel to ${commandName}`);
|
||||
if (!parallelLimit[commandName]) parallelLimit[commandName] = 0;
|
||||
|
||||
const prevNumber = parallelLimit[commandName];
|
||||
|
||||
// console.log(`[ADD] Previous parallel executions: ${prevNumber}`);
|
||||
// console.log(`[ADD] Current parallel executions: ${JSON.stringify(parallelLimit)}`);
|
||||
parallelLimit[commandName] = prevNumber + 1;
|
||||
}
|
||||
|
||||
async function removeParallel(commandName) {
|
||||
// console.log(`[REMOVE] Removing parallel to ${commandName}`);
|
||||
|
||||
// This shouldn't be possible
|
||||
if (!parallelLimit[commandName]) parallelLimit[commandName] = 0;
|
||||
|
||||
const prevNumber = parallelLimit[commandName];
|
||||
|
||||
// console.log(`[REMOVE] previous number: ${prevNumber}`);
|
||||
// console.log(`[REMOVE] previous parallel limit: ${JSON.stringify(parallelLimit)}`);
|
||||
parallelLimit[commandName] = prevNumber - 1;
|
||||
// console.log(`[REMOVE] current parallel limit: ${JSON.stringify(parallelLimit)}`);
|
||||
}
|
||||
|
||||
async function checkParallel(user, commandName, command) {
|
||||
// Don't apply the rate limit to bot owner
|
||||
if (NODE_ENV !== 'development') {
|
||||
if (user.id === ownerId) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!parallelLimit[commandName]) parallelLimit[commandName] = 0;
|
||||
|
||||
// console.log(`[CHECK] command limit: ${command.parallelLimit}`);
|
||||
// console.log(`[CHECK] current parallel executions: ${parallelLimit[commandName]}`);
|
||||
if (parallelLimit[commandName] >= command.parallelLimit) {
|
||||
return { limited: true, current: parallelLimit[commandName], max: command.parallelLimit, msg: `There are currently too many parallel execution of this command, please wait before retrying. (${parallelLimit[commandName]}/${command.parallelLimit})` };
|
||||
}
|
||||
|
||||
return { limited: false, current: parallelLimit[commandName], max: command.parallelLimit };
|
||||
}
|
110
utils/videos.js
110
utils/videos.js
|
@ -1,6 +1,6 @@
|
|||
import os from 'node:os';
|
||||
import { exec } from 'node:child_process';
|
||||
const { NODE_ENV } = process.env;
|
||||
import { execFile } from 'node:child_process';
|
||||
const { NODE_ENV, ytdlpMaxResolution, proxy } = process.env;
|
||||
|
||||
export default {
|
||||
downloadVideo,
|
||||
|
@ -9,25 +9,35 @@ export default {
|
|||
stringIsAValidurl,
|
||||
compressVideo,
|
||||
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) => {
|
||||
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) {
|
||||
reject(stderr);
|
||||
return reject(stderr);
|
||||
}
|
||||
if (stderr) {
|
||||
console.error(stderr);
|
||||
// Should already be rejected at that points
|
||||
return reject(stderr);
|
||||
}
|
||||
console.log(NODE_ENV === 'development' ? stdout : null);
|
||||
resolve();
|
||||
return resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function upload(file) {
|
||||
return await new Promise((resolve, reject) => {
|
||||
exec(`./bin/upload.sh ${file}`, (err, stdout, stderr) => {
|
||||
execFile('./bin/upload.sh', [file], (err, stdout, stderr) => {
|
||||
if (err) {
|
||||
reject(stderr);
|
||||
}
|
||||
|
@ -41,7 +51,7 @@ async function upload(file) {
|
|||
|
||||
async function ffmpeg(command) {
|
||||
return await new Promise((resolve, reject) => {
|
||||
exec(`ffmpeg ${command}`, (err, stdout, stderr) => {
|
||||
execFile('ffmpeg', ['-hide_banner', ...command], (err, stdout, stderr) => {
|
||||
if (err) {
|
||||
reject(stderr);
|
||||
}
|
||||
|
@ -66,7 +76,7 @@ async function stringIsAValidurl(s) {
|
|||
|
||||
async function compressVideo(input, output, preset) {
|
||||
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) {
|
||||
reject(stderr);
|
||||
}
|
||||
|
@ -80,7 +90,7 @@ async function compressVideo(input, output, preset) {
|
|||
}
|
||||
async function getVideoCodec(input) {
|
||||
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) {
|
||||
reject(stderr);
|
||||
}
|
||||
|
@ -91,3 +101,83 @@ async function getVideoCodec(input) {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
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