forked from Supositware/Haha-Yes
Compare commits
276 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 | |||
f8c958af91 | |||
132d387e49 | |||
fa95596906 | |||
5b2fa020d1 | |||
c0fad4c460 | |||
2f4caf0e72 | |||
81e96974b9 | |||
a48a696d50 | |||
e50a97848f | |||
9ef244fc3f | |||
bd7f0d12e5 | |||
b960829e72 | |||
60e5152bd9 | |||
29e97c27f6 | |||
f29d721771 | |||
cfad048b8e | |||
fa3671efdf | |||
ffc51c5503 | |||
a0de902935 | |||
c237f2fb7c | |||
0b63dd3a60 | |||
c66f3e8403 | |||
008029b49f | |||
babfd52eca | |||
6fa8f7d33d | |||
d45ac9be4f | |||
3e236cc580 | |||
0b01712712 | |||
2f0d6d6a42 | |||
f8cf11e7ff | |||
2c37b3a64f | |||
5311aa5847 | |||
0c793d73a6 | |||
7254a242db | |||
d225d7037c | |||
fcb7776f60 | |||
467d282a3c | |||
56368acf74 | |||
a8e98740b3 | |||
df722f8ffb | |||
cfa6a55d39 | |||
8f9bb6b4f8 | |||
f5f7b48935 | |||
769fecfd87 | |||
0a3de197ff | |||
a6449b93e4 | |||
3b60fee0df | |||
5a6119bc42 | |||
2f7c03e011 | |||
983bfcfc9e | |||
c752eebd9a | |||
24391ec40c | |||
900a633f0d | |||
9e621a88e8 | |||
1c3c9a6cca | |||
06d0d3d5c5 | |||
5a8ec1dbe7 | |||
332f1730d6 | |||
c5cacbb78b | |||
5b426b55ff | |||
cc35373749 | |||
c908a524aa | |||
16ddd8b388 | |||
cc25953e90 | |||
366e15f7e4 | |||
bc3e596356 | |||
0ae54bbbca | |||
118954f795 | |||
e1c6c9dc39 | |||
458e913ad0 | |||
ac93981fd6 | |||
0420fcd50c | |||
424d5ab02a | |||
2714c748bf | |||
eecb6020ea | |||
605390bfe2 | |||
91b3b6bc1e | |||
9dd060aa28 | |||
66736a5557 | |||
92c91928e7 | |||
6c49cc548c | |||
5a6f785c61 | |||
e926034644 | |||
bf58138205 | |||
5cb52fb371 | |||
56768640ac | |||
3143e478cd | |||
1a87dbb325 | |||
fb31f38c97 | |||
226ae3b096 | |||
1eb3b8a013 | |||
b637a73a85 | |||
f13c1f3ca2 | |||
fcc23324f2 | |||
c4574e47f6 | |||
d4a56009fd | |||
d1e8dda148 | |||
b9b7b02dc1 | |||
230b77f2ee | |||
f4772274ac | |||
7c39b62d2c | |||
9dcdf9f188 | |||
7e17f7564e | |||
cdebcd92a0 | |||
a1f995855d | |||
138ddb261c | |||
0ecafafc96 | |||
043c18b216 | |||
4abff9771f | |||
22079fcc4e | |||
84aaeefe00 | |||
5a43d46975 | |||
8dab14444a | |||
1edb4d8fef | |||
8ae824f025 | |||
009f365728 | |||
59f0f85df4 | |||
287b89332e | |||
02c1b4b2f7 | |||
189483d31e | |||
7160681931 | |||
4915420c90 | |||
8d59ac37a6 | |||
f82ff3d380 | |||
9765680f1c | |||
a73fd2b893 | |||
4846604fdf | |||
63162ebe6d | |||
738b5bb4ff | |||
5f6cd0ea9e | |||
88a8bff37d | |||
b0397fff78 | |||
6902a10a97 | |||
23d230104b | |||
1d9bcc9597 | |||
18607ba90e | |||
069639a4c5 | |||
d36d086388 | |||
c3e8ea2152 | |||
2b1f5e4d07 | |||
850a6fb827 | |||
0f64fd79f2 | |||
c24520dd24 | |||
099d0d75e4 | |||
1851a29cd0 | |||
73e5050ded | |||
afed8e9108 | |||
7db77c9916 | |||
03b3b9189e | |||
8e9e93ca50 | |||
d00ddbbbf5 | |||
01a2c9bab5 | |||
7e186a07a9 | |||
6272cde878 | |||
ef61db520b | |||
bb49cfd490 |
105 changed files with 6839 additions and 4458 deletions
11
.env.example
11
.env.example
|
@ -2,7 +2,7 @@ token=YourToken
|
||||||
clientId=BotClientId
|
clientId=BotClientId
|
||||||
guildId=DevGuildId
|
guildId=DevGuildId
|
||||||
ownerId=OwnerUserId
|
ownerId=OwnerUserId
|
||||||
statusChannel=
|
statusChannel=CHannelIdForStatus
|
||||||
uptimeURL=UptimeKumaOrWhateverStatusThingYouUseOrJustLeaveEmpty
|
uptimeURL=UptimeKumaOrWhateverStatusThingYouUseOrJustLeaveEmpty
|
||||||
uptimeInterval=60
|
uptimeInterval=60
|
||||||
twiConsumer=TwitterConsumerToken
|
twiConsumer=TwitterConsumerToken
|
||||||
|
@ -10,4 +10,11 @@ twiConsumerSecret=TwitterConsumerSecretToken
|
||||||
twiToken=TwitterToken
|
twiToken=TwitterToken
|
||||||
twiTokenSecret=TwitterSecretToken
|
twiTokenSecret=TwitterSecretToken
|
||||||
twiChannel=ChannelWhereJustTheTwitterLinkAreSent
|
twiChannel=ChannelWhereJustTheTwitterLinkAreSent
|
||||||
twiLogChannel=ChannelWhereTheDetailedInfoOfTheCommandIsSent
|
twiLogChannel=ChannelWhereTheDetailedInfoOfTheCommandIsSent
|
||||||
|
botsggToken=APITokenForBots.gg
|
||||||
|
botsggEndpoint=https://discord.bots.gg/api/v1
|
||||||
|
stableHordeApi=0000000000
|
||||||
|
stableHordeID=0000
|
||||||
|
NODE_ENV=development
|
||||||
|
ytdlpMaxResolution=720
|
||||||
|
proxy=socks5://localhost:3128
|
|
@ -1,57 +0,0 @@
|
||||||
{
|
|
||||||
"extends": "eslint:recommended",
|
|
||||||
"env": {
|
|
||||||
"node": true,
|
|
||||||
"es6": true
|
|
||||||
},
|
|
||||||
"parser": "@babel/eslint-parser",
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": 2022,
|
|
||||||
"sourceType": "module",
|
|
||||||
"requireConfigFile": false,
|
|
||||||
"babelOptions": {
|
|
||||||
"plugins": [
|
|
||||||
"@babel/plugin-syntax-import-assertions"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"arrow-spacing": ["warn", { "before": true, "after": true }],
|
|
||||||
"brace-style": ["error", "stroustrup", { "allowSingleLine": true }],
|
|
||||||
"comma-dangle": ["error", "always-multiline"],
|
|
||||||
"comma-spacing": "error",
|
|
||||||
"comma-style": "error",
|
|
||||||
"curly": ["error", "multi-line", "consistent"],
|
|
||||||
"dot-location": ["error", "property"],
|
|
||||||
"handle-callback-err": "off",
|
|
||||||
"indent": ["error", "tab"],
|
|
||||||
"keyword-spacing": "error",
|
|
||||||
"max-nested-callbacks": ["error", { "max": 4 }],
|
|
||||||
"max-statements-per-line": ["error", { "max": 2 }],
|
|
||||||
"no-console": "off",
|
|
||||||
"no-empty-function": "error",
|
|
||||||
"no-floating-decimal": "error",
|
|
||||||
"no-inline-comments": "error",
|
|
||||||
"no-lonely-if": "error",
|
|
||||||
"no-multi-spaces": "error",
|
|
||||||
"no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF": 0 }],
|
|
||||||
"no-shadow": ["error", { "allow": ["err", "resolve", "reject"] }],
|
|
||||||
"no-trailing-spaces": ["error"],
|
|
||||||
"no-var": "error",
|
|
||||||
"object-curly-spacing": ["error", "always"],
|
|
||||||
"prefer-const": "error",
|
|
||||||
"quotes": ["error", "single"],
|
|
||||||
"semi": ["error", "always"],
|
|
||||||
"space-before-blocks": "error",
|
|
||||||
"space-before-function-paren": ["error", {
|
|
||||||
"anonymous": "never",
|
|
||||||
"named": "never",
|
|
||||||
"asyncArrow": "always"
|
|
||||||
}],
|
|
||||||
"space-in-parens": "error",
|
|
||||||
"space-infix-ops": "error",
|
|
||||||
"space-unary-ops": "error",
|
|
||||||
"spaced-comment": "error",
|
|
||||||
"yoda": "error"
|
|
||||||
}
|
|
||||||
}
|
|
2
.github/ISSUE_TEMPLATE/bug.md
vendored
2
.github/ISSUE_TEMPLATE/bug.md
vendored
|
@ -33,5 +33,5 @@ labels:
|
||||||
|
|
||||||
**Did someone already report that bug?**
|
**Did someone already report that bug?**
|
||||||
|
|
||||||
- [ ] Yes <!-- If you have to put yes you don't need to submit that feature request. -->
|
- [ ] Yes <!-- If you have to put yes you don't need to submit that bug report. -->
|
||||||
- [ ] No
|
- [ ] No
|
||||||
|
|
14
.gitignore
vendored
14
.gitignore
vendored
|
@ -1,4 +1,16 @@
|
||||||
.env
|
.env
|
||||||
node_modules/
|
node_modules/
|
||||||
bin/
|
|
||||||
config/config.json
|
config/config.json
|
||||||
|
json/board/
|
||||||
|
unloaded/
|
||||||
|
database.sqlite3
|
||||||
|
tmp/*.js
|
||||||
|
|
||||||
|
bin/yt-dlp*
|
||||||
|
bin/HandBrakeCLI*
|
||||||
|
bin/upload.sh
|
||||||
|
bin/dectalk
|
||||||
|
|
||||||
|
asset/ytp/sources
|
||||||
|
asset/ytp/music
|
||||||
|
asset/ytp/sounds
|
BIN
asset/ytp/error1.mp4
Normal file
BIN
asset/ytp/error1.mp4
Normal file
Binary file not shown.
BIN
asset/ytp/error2.mp4
Normal file
BIN
asset/ytp/error2.mp4
Normal file
Binary file not shown.
BIN
asset/ytp/intro.mp4
Normal file
BIN
asset/ytp/intro.mp4
Normal file
Binary file not shown.
BIN
asset/ytp/outro.mp4
Normal file
BIN
asset/ytp/outro.mp4
Normal file
Binary file not shown.
0
asset/ytp/userVid/Video for ytp command goes here
Normal file
0
asset/ytp/userVid/Video for ytp command goes here
Normal file
0
bin/.keep
Normal file
0
bin/.keep
Normal file
171
commands/AI/img2img.js
Normal file
171
commands/AI/img2img.js
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
/* TODO
|
||||||
|
*
|
||||||
|
* To be merged with commands/AI/txt2img.js
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import { SlashCommandBuilder, EmbedBuilder, AttachmentBuilder, ButtonBuilder, ActionRowBuilder, ButtonStyle } from 'discord.js';
|
||||||
|
import fetch from 'node-fetch';
|
||||||
|
import os from 'node:os';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import stream from 'node:stream';
|
||||||
|
import util from 'node:util';
|
||||||
|
|
||||||
|
import db from '../../models/index.js';
|
||||||
|
|
||||||
|
const { stableHordeApi, stableHordeID } = process.env;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('img2img')
|
||||||
|
.setDescription('AI generated image with stable diffusion (If credit are low it may be slow)')
|
||||||
|
.addAttachmentOption(option =>
|
||||||
|
option.setName('image')
|
||||||
|
.setDescription('Image you want to modify')
|
||||||
|
.setRequired(true))
|
||||||
|
.addStringOption(option =>
|
||||||
|
option.setName('prompt')
|
||||||
|
.setDescription('What do you want the AI to generate?')
|
||||||
|
.setRequired(true)),
|
||||||
|
category: 'AI',
|
||||||
|
alias: ['i2i'],
|
||||||
|
async execute(interaction, args, client) {
|
||||||
|
await interaction.deferReply();
|
||||||
|
|
||||||
|
const streamPipeline = util.promisify(stream.pipeline);
|
||||||
|
const res = await fetch(args.image.url);
|
||||||
|
if (!res.ok) return interaction.editReply('An error has occured while trying to download your image.');
|
||||||
|
await streamPipeline(res.body, fs.createWriteStream(`${os.tmpdir()}/${args.image.name}.webp`));
|
||||||
|
|
||||||
|
const b64Image = fs.readFileSync(`${os.tmpdir()}/${args.image.name}.webp`, { encoding: 'base64' });
|
||||||
|
generate(interaction, args.prompt, client, b64Image);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async function generate(i, prompt, client, b64Img) {
|
||||||
|
console.log('Generating image');
|
||||||
|
const body = {
|
||||||
|
prompt: prompt,
|
||||||
|
params: {
|
||||||
|
n: 1,
|
||||||
|
width: 512,
|
||||||
|
height: 512,
|
||||||
|
},
|
||||||
|
cfg_scale: 9,
|
||||||
|
use_gfpgan: true,
|
||||||
|
use_real_esrgan: true,
|
||||||
|
use_ldsr: true,
|
||||||
|
use_upscaling: true,
|
||||||
|
steps: 50,
|
||||||
|
nsfw: i.channel.nsfw ? true : false,
|
||||||
|
censor_nsfw: i.channel.nsfw ? true : false,
|
||||||
|
source_image: b64Img,
|
||||||
|
source_processing: 'img2img',
|
||||||
|
shared: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const isOptOut = await db.optout.findOne({ where: { userID: i.user.id } });
|
||||||
|
|
||||||
|
if (isOptOut) {
|
||||||
|
body.shared = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchParameters = {
|
||||||
|
method: 'post',
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
headers: { 'Content-Type': 'application/json', 'apikey': stableHordeApi },
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = await fetch('https://stablehorde.net/api/v2/generate/async', fetchParameters);
|
||||||
|
|
||||||
|
response = await response.json();
|
||||||
|
|
||||||
|
if (!response.id) {
|
||||||
|
console.log(response);
|
||||||
|
return i.editReply({ content: `An error has occured, please try again later. \`${response.message}\`` });
|
||||||
|
}
|
||||||
|
|
||||||
|
let wait_time = 5000;
|
||||||
|
let checkURL = `https://stablehorde.net/api/v2/generate/check/${response.id}`;
|
||||||
|
const checking = setInterval(async () => {
|
||||||
|
const checkResult = await checkGeneration(checkURL);
|
||||||
|
|
||||||
|
if (checkResult === undefined) return;
|
||||||
|
if (!checkResult.done) {
|
||||||
|
if (checkResult.wait_time === -1) {
|
||||||
|
console.log(checkResult.raw);
|
||||||
|
return i.editReply({ content: `An error has occured, please try again later. \`${checkResult.raw.message}\`` });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkResult.wait_time < 0) {
|
||||||
|
console.log(checkResult.raw);
|
||||||
|
clearInterval(checking);
|
||||||
|
return i.editReply({ content: 'No servers are currently available to fulfill your request, please try again later.' });
|
||||||
|
}
|
||||||
|
if (checkResult.wait_time === 0) {
|
||||||
|
checkURL = `https://stablehorde.net/api/v2/generate/status/${response.id}`;
|
||||||
|
}
|
||||||
|
wait_time = checkResult.wait_time;
|
||||||
|
}
|
||||||
|
else if (checkResult.done && checkResult.image) {
|
||||||
|
clearInterval(checking);
|
||||||
|
let creditResponse = await fetch(`https://stablehorde.net/api/v2/users/${stableHordeID}`);
|
||||||
|
creditResponse = await creditResponse.json();
|
||||||
|
|
||||||
|
const streamPipeline = util.promisify(stream.pipeline);
|
||||||
|
const res = await fetch(checkResult.image);
|
||||||
|
if (!res.ok) return i.editReply('An error has occured while trying to download your image.');
|
||||||
|
await streamPipeline(res.body, fs.createWriteStream(`${os.tmpdir()}/${i.id}.webp`));
|
||||||
|
|
||||||
|
const generatedImg = new AttachmentBuilder(`${os.tmpdir()}/${i.id}.webp`);
|
||||||
|
|
||||||
|
const stableEmbed = new EmbedBuilder()
|
||||||
|
.setColor(i.member ? i.member.displayHexColor : 'Navy')
|
||||||
|
.setTitle(prompt)
|
||||||
|
.setURL('https://aqualxx.github.io/stable-ui/')
|
||||||
|
.setImage(`attachment://${i.id}.webp`)
|
||||||
|
.setFooter({ text: `**Credit left: ${creditResponse.kudos}** Seed: ${checkResult.seed} worker ID: ${checkResult.worker_id} worker name: ${checkResult.worker_name}` });
|
||||||
|
|
||||||
|
const row = new ActionRowBuilder()
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`regenerate${i.user.id}${i.id}`)
|
||||||
|
.setLabel('🔄 Regenerate')
|
||||||
|
.setStyle(ButtonStyle.Primary),
|
||||||
|
);
|
||||||
|
|
||||||
|
await i.editReply({ embeds: [stableEmbed], components: [row], files: [generatedImg] });
|
||||||
|
|
||||||
|
listenButton(client, i, prompt);
|
||||||
|
}
|
||||||
|
}, wait_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkGeneration(url) {
|
||||||
|
let check = await fetch(url);
|
||||||
|
check = await check.json();
|
||||||
|
|
||||||
|
if (!check.is_possible) {
|
||||||
|
return { done: false, wait_time: -1, raw: check };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (check.done) {
|
||||||
|
if (!check.generations) {
|
||||||
|
return { done: false, wait_time: check.wait_time * 1000, raw: check };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { done: true, image: check.generations[0].img, seed: check.generations[0].seed, worker_id: check.generations[0].worker_id, worker_name: check.generations[0].worker_name, raw: check };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listenButton(client, interaction, prompt) {
|
||||||
|
client.once('interactionCreate', async (interactionMenu) => {
|
||||||
|
if (!interactionMenu.isButton()) return;
|
||||||
|
|
||||||
|
await interactionMenu.update({ components: [] });
|
||||||
|
|
||||||
|
if (interactionMenu.customId === `regenerate${interactionMenu.user.id}${interaction.id}`) {
|
||||||
|
await interactionMenu.deferReply();
|
||||||
|
await generate(interactionMenu, prompt, client);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
145
commands/AI/txt2img.js
Normal file
145
commands/AI/txt2img.js
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
/* TODO
|
||||||
|
*
|
||||||
|
* To be merged with commands/AI/img2img.js
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
import { SlashCommandBuilder, EmbedBuilder, AttachmentBuilder, ButtonBuilder, ActionRowBuilder, ButtonStyle } from 'discord.js';
|
||||||
|
import fetch from 'node-fetch';
|
||||||
|
import os from 'node:os';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import stream from 'node:stream';
|
||||||
|
import util from 'node:util';
|
||||||
|
|
||||||
|
import db from '../../models/index.js';
|
||||||
|
|
||||||
|
const { stableHordeApi, stableHordeID } = process.env;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('txt2img')
|
||||||
|
.setDescription('AI generated image with stable diffusion (If credit are low it may be slow)')
|
||||||
|
.addStringOption(option =>
|
||||||
|
option.setName('prompt')
|
||||||
|
.setDescription('What do you want the AI to generate?')
|
||||||
|
.setRequired(true)),
|
||||||
|
category: 'AI',
|
||||||
|
alias: ['t2i'],
|
||||||
|
async execute(interaction, args, client) {
|
||||||
|
await interaction.deferReply();
|
||||||
|
generate(interaction, args.prompt, client);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async function generate(i, prompt, client) {
|
||||||
|
const body = {
|
||||||
|
prompt: prompt,
|
||||||
|
params: {
|
||||||
|
n: 1,
|
||||||
|
width: 512,
|
||||||
|
height: 512,
|
||||||
|
},
|
||||||
|
cfg_scale: 9,
|
||||||
|
use_gfpgan: true,
|
||||||
|
use_real_esrgan: true,
|
||||||
|
use_ldsr: true,
|
||||||
|
use_upscaling: true,
|
||||||
|
steps: 50,
|
||||||
|
nsfw: i.channel.nsfw ? true : false,
|
||||||
|
censor_nsfw: i.channel.nsfw ? true : false,
|
||||||
|
shared: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const isOptOut = await db.optout.findOne({ where: { userID: i.user.id } });
|
||||||
|
|
||||||
|
if (isOptOut) {
|
||||||
|
body.shared = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchParameters = {
|
||||||
|
method: 'post',
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
headers: { 'Content-Type': 'application/json', 'apikey': stableHordeApi },
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = await fetch('https://stablehorde.net/api/v2/generate/async', fetchParameters);
|
||||||
|
|
||||||
|
response = await response.json();
|
||||||
|
let wait_time = 5000;
|
||||||
|
let checkURL = `https://stablehorde.net/api/v2/generate/check/${response.id}`;
|
||||||
|
const checking = setInterval(async () => {
|
||||||
|
const checkResult = await checkGeneration(checkURL);
|
||||||
|
|
||||||
|
if (checkResult === undefined) return;
|
||||||
|
if (!checkResult.done) {
|
||||||
|
if (checkResult.wait_time < 0) {
|
||||||
|
clearInterval(checking);
|
||||||
|
return i.editReply({ content: 'No servers are currently available to fulfill your request, please try again later.' });
|
||||||
|
}
|
||||||
|
if (checkResult.wait_time === 0) {
|
||||||
|
checkURL = `https://stablehorde.net/api/v2/generate/status/${response.id}`;
|
||||||
|
}
|
||||||
|
wait_time = checkResult.wait_time;
|
||||||
|
}
|
||||||
|
else if (checkResult.done && checkResult.image) {
|
||||||
|
clearInterval(checking);
|
||||||
|
let creditResponse = await fetch(`https://stablehorde.net/api/v2/users/${stableHordeID}`);
|
||||||
|
creditResponse = await creditResponse.json();
|
||||||
|
|
||||||
|
const streamPipeline = util.promisify(stream.pipeline);
|
||||||
|
const res = await fetch(checkResult.image);
|
||||||
|
if (!res.ok) return i.editReply('An error has occured while trying to download your image.');
|
||||||
|
await streamPipeline(res.body, fs.createWriteStream(`${os.tmpdir()}/${i.id}.webp`));
|
||||||
|
|
||||||
|
const generatedImg = new AttachmentBuilder(`${os.tmpdir()}/${i.id}.webp`);
|
||||||
|
|
||||||
|
const stableEmbed = new EmbedBuilder()
|
||||||
|
.setColor(i.member ? i.member.displayHexColor : 'Navy')
|
||||||
|
.setTitle(prompt)
|
||||||
|
.setURL('https://aqualxx.github.io/stable-ui/')
|
||||||
|
.setImage(`attachment://${i.id}.webp`)
|
||||||
|
.setFooter({ text: `**Credit left: ${creditResponse.kudos}** Seed: ${checkResult.seed} worker ID: ${checkResult.worker_id} worker name: ${checkResult.worker_name}` });
|
||||||
|
|
||||||
|
const row = new ActionRowBuilder()
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`regenerate${i.user.id}${i.id}`)
|
||||||
|
.setLabel('🔄 Regenerate')
|
||||||
|
.setStyle(ButtonStyle.Primary),
|
||||||
|
);
|
||||||
|
|
||||||
|
await i.editReply({ embeds: [stableEmbed], components: [row], files: [generatedImg] });
|
||||||
|
|
||||||
|
listenButton(client, i, prompt);
|
||||||
|
}
|
||||||
|
}, wait_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkGeneration(url) {
|
||||||
|
let check = await fetch(url);
|
||||||
|
check = await check.json();
|
||||||
|
|
||||||
|
if (!check.is_possible) {
|
||||||
|
return { done: false, wait_time: -1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (check.done) {
|
||||||
|
if (!check.generations) {
|
||||||
|
return { done: false, wait_time: check.wait_time * 1000 };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { done: true, image: check.generations[0].img, seed: check.generations[0].seed, worker_id: check.generations[0].worker_id, worker_name: check.generations[0].worker_name };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listenButton(client, interaction, prompt) {
|
||||||
|
client.once('interactionCreate', async (interactionMenu) => {
|
||||||
|
if (!interactionMenu.isButton()) return;
|
||||||
|
|
||||||
|
await interactionMenu.update({ components: [] });
|
||||||
|
|
||||||
|
if (interactionMenu.customId === `regenerate${interactionMenu.user.id}${interaction.id}`) {
|
||||||
|
await interactionMenu.deferReply();
|
||||||
|
await generate(interactionMenu, prompt, client);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
62
commands/admin/autoresponse.js
Normal file
62
commands/admin/autoresponse.js
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import { SlashCommandBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder, PermissionFlagsBits } from 'discord.js';
|
||||||
|
import db from '../../models/index.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('autoresponse')
|
||||||
|
.setDescription('Enable or disable autoresponse')
|
||||||
|
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages),
|
||||||
|
category: 'admin',
|
||||||
|
async execute(interaction, args, client) {
|
||||||
|
const autoresponseStat = await db.autoresponseStat.findOne({ where: { serverID: interaction.guild.id } });
|
||||||
|
if (!autoresponseStat) {
|
||||||
|
const body = { serverID: interaction.guild.id, stat: 'enable' };
|
||||||
|
await db.autoresponseStat.create(body);
|
||||||
|
return await interaction.reply({ content: 'Autoresponse has been enabled.', ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const row = new ActionRowBuilder()
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`yes${interaction.user.id}${interaction.id}`)
|
||||||
|
.setLabel('Yes')
|
||||||
|
.setStyle(ButtonStyle.Primary),
|
||||||
|
)
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`no${interaction.user.id}${interaction.id}`)
|
||||||
|
.setLabel('No')
|
||||||
|
.setStyle(ButtonStyle.Danger),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (autoresponseStat.stat === 'enable') {
|
||||||
|
await interaction.reply({ content: 'Autoresponse is already enabled, do you wish to disable it?', components: [row], ephemeral: true });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const body = { serverID: interaction.guild.id, stat: 'enable' };
|
||||||
|
await db.autoresponseStat.update(body, { where: { serverID: interaction.guild.id } });
|
||||||
|
return interaction.editReply({ content: 'Auto response has been enabled.', ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
return listenButton(client, interaction, interaction.user);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async function listenButton(client, interaction, user = interaction.user, originalId = interaction.id) {
|
||||||
|
client.once('interactionCreate', async (interactionMenu) => {
|
||||||
|
if (user !== interactionMenu.user) return listenButton(client, interaction, user, originalId);
|
||||||
|
if (!interactionMenu.isButton()) return;
|
||||||
|
|
||||||
|
await interactionMenu.update({ components: [] });
|
||||||
|
|
||||||
|
if (interactionMenu.customId === `yes${interaction.user.id}${originalId}`) {
|
||||||
|
const body = { serverID: interaction.guild.id, stat: 'disable' };
|
||||||
|
await db.autoresponseStat.update(body, { where: { serverID: interaction.guild.id } });
|
||||||
|
return interaction.editReply({ content: 'Auto response has been disabled.', ephemeral: true });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
74
commands/admin/bye.js
Normal file
74
commands/admin/bye.js
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
import { SlashCommandBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder, PermissionFlagsBits } from 'discord.js';
|
||||||
|
import db from '../../models/index.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('bye')
|
||||||
|
.setDescription('Set a leave message')
|
||||||
|
.addStringOption(option =>
|
||||||
|
option.setName('message')
|
||||||
|
.setDescription('The message you want the bot to say when someone leave in the current channel.')),
|
||||||
|
category: 'admin',
|
||||||
|
userPermissions: [PermissionFlagsBits.ManageChannels],
|
||||||
|
async execute(interaction, args, client) {
|
||||||
|
const leave = await db.leaveChannel.findOne({ where: { guildID: interaction.guild.id } });
|
||||||
|
|
||||||
|
if (!leave && !args.message) {
|
||||||
|
return interaction.reply({ content: 'You need a message for me to say anything!', ephemeral: true });
|
||||||
|
}
|
||||||
|
else if (!leave) {
|
||||||
|
const body = { guildID: interaction.guild.id, channelID: interaction.channel.id, message: args.message };
|
||||||
|
await db.leaveChannel.create(body);
|
||||||
|
return interaction.reply({ content: `The leave message have been set with ${args.message}`, ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const row = new ActionRowBuilder()
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`edit${interaction.user.id}${interaction.id}`)
|
||||||
|
.setLabel('Edit')
|
||||||
|
.setStyle(ButtonStyle.Primary),
|
||||||
|
)
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`remove${interaction.user.id}${interaction.id}`)
|
||||||
|
.setLabel('Remove')
|
||||||
|
.setStyle(ButtonStyle.Danger),
|
||||||
|
)
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`nothing${interaction.user.id}${interaction.id}`)
|
||||||
|
.setLabel('Do nothing')
|
||||||
|
.setStyle(ButtonStyle.Secondary),
|
||||||
|
);
|
||||||
|
|
||||||
|
await interaction.reply({ content: 'The server already has a message set, do you want to edit it or remove it?', components: [row], ephemeral: true });
|
||||||
|
|
||||||
|
return listenButton(client, interaction, args, interaction.user);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async function listenButton(client, interaction, args, user = interaction.user, originalId = interaction.id) {
|
||||||
|
client.once('interactionCreate', async (interactionMenu) => {
|
||||||
|
if (user !== interactionMenu.user) return listenButton(client, interaction, args, user, originalId);
|
||||||
|
if (!interactionMenu.isButton()) return;
|
||||||
|
|
||||||
|
await interactionMenu.update({ components: [] });
|
||||||
|
|
||||||
|
if (interactionMenu.customId === `edit${interaction.user.id}${originalId}`) {
|
||||||
|
if (!args.message) {
|
||||||
|
return interaction.reply({ content: 'You need to input a message for me to edit!', ephemeral: true });
|
||||||
|
}
|
||||||
|
const body = { guildID: interaction.guild.id, channelID: interaction.channel.id, message: args.message };
|
||||||
|
await db.leaveChannel.update(body, { where: { guildID: interaction.guild.id } });
|
||||||
|
return interaction.editReply({ content: `The leave message has been set to ${args.message}`, ephemeral: true });
|
||||||
|
}
|
||||||
|
else if (interactionMenu.customId === `remove${interaction.user.id}${originalId}`) {
|
||||||
|
db.leaveChannel.destroy({ where: { guildID: interaction.guild.id, channelID: interaction.channel.id } });
|
||||||
|
return interaction.editReply({ content: 'The leave message has been deleted.', ephemeral: true });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
62
commands/admin/quotation.js
Normal file
62
commands/admin/quotation.js
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import { SlashCommandBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder, PermissionFlagsBits } from 'discord.js';
|
||||||
|
import db from '../../models/index.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('quotation')
|
||||||
|
.setDescription('Enable or disable quotations')
|
||||||
|
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages),
|
||||||
|
category: 'admin',
|
||||||
|
async execute(interaction, args, client) {
|
||||||
|
const quotationstat = await db.quotationStat.findOne({ where: { serverID: interaction.guild.id } });
|
||||||
|
|
||||||
|
if (!quotationstat) {
|
||||||
|
const body = { serverID: interaction.guild.id, stat: 'enable' };
|
||||||
|
await db.quotationStat.create(body);
|
||||||
|
return await interaction.reply({ content: 'Quotation has been enabled.', ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const row = new ActionRowBuilder()
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`yes${interaction.user.id}${interaction.id}`)
|
||||||
|
.setLabel('Yes')
|
||||||
|
.setStyle(ButtonStyle.Primary),
|
||||||
|
)
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`no${interaction.user.id}${interaction.id}`)
|
||||||
|
.setLabel('No')
|
||||||
|
.setStyle(ButtonStyle.Danger),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (quotationstat.stat === 'enable') {
|
||||||
|
await interaction.reply({ content: 'Quotation is already enabled, do you wish to disable it?', components: [row], ephemeral: true });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const body = { serverID: interaction.guild.id, stat: 'enable' };
|
||||||
|
await db.autoresponseStat.update(body, { where: { serverID: interaction.guild.id } });
|
||||||
|
return interaction.editReply({ content: 'Quotation has been enabled.', ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
return listenButton(client, interaction, interaction.user);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async function listenButton(client, interaction, user = interaction.user, originalId = interaction.id) {
|
||||||
|
client.once('interactionCreate', async (interactionMenu) => {
|
||||||
|
if (user !== interactionMenu.user) return listenButton(client, interaction, user, originalId);
|
||||||
|
if (!interactionMenu.isButton()) return;
|
||||||
|
|
||||||
|
interactionMenu.update({ components: [] });
|
||||||
|
|
||||||
|
if (interactionMenu.customId === `yes${interaction.user.id}${originalId}`) {
|
||||||
|
await db.quotationStat.destroy({ where: { serverID: interaction.guild.id } });
|
||||||
|
return interaction.editReply({ content: 'Quotation has been disabled.', ephemeral: true });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
38
commands/admin/shameboard.js
Normal file
38
commands/admin/shameboard.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('shameboard')
|
||||||
|
.setDescription('Set shameboard to the current channel.')
|
||||||
|
.addStringOption(option =>
|
||||||
|
option.setName('emote')
|
||||||
|
.setDescription('The emote that should be used to enter the shameboard.'))
|
||||||
|
.addStringOption(option =>
|
||||||
|
option.setName('count')
|
||||||
|
.setDescription('How many react for it to enter shameboard.'))
|
||||||
|
.addBooleanOption(option =>
|
||||||
|
option.setName('remove')
|
||||||
|
.setDescription('Remove the shameboard')
|
||||||
|
.setRequired(false)),
|
||||||
|
category: 'admin',
|
||||||
|
userPermissions: [PermissionFlagsBits.ManageChannels],
|
||||||
|
async execute(interaction, args) {
|
||||||
|
if (args.remove) {
|
||||||
|
fs.unlink(`./json/board/shame${interaction.guild.id}.json`, (err) => {
|
||||||
|
if (err) {return interaction.reply('There is no shameboard');}
|
||||||
|
return interaction.reply('Deleted the shameboard');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!args.emote || !args.count) return interaction.reply('You are missing the emote or the count arg!');
|
||||||
|
fs.writeFile(`./json/board/shame${interaction.guild.id}.json`, `{"shameboard": "${interaction.channel.id}", "emote": "${args.emote}", "count": "${args.count}"}`, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return interaction.reply(`This channel have been set as the shameboard with ${args.emote} with the minimum of ${args.count}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
38
commands/admin/starboard.js
Normal file
38
commands/admin/starboard.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('starboard')
|
||||||
|
.setDescription('Set starboard to the current channel.')
|
||||||
|
.addStringOption(option =>
|
||||||
|
option.setName('emote')
|
||||||
|
.setDescription('The emote that should be used to enter the starboard.'))
|
||||||
|
.addStringOption(option =>
|
||||||
|
option.setName('count')
|
||||||
|
.setDescription('How many react for it to enter starboard.'))
|
||||||
|
.addBooleanOption(option =>
|
||||||
|
option.setName('remove')
|
||||||
|
.setDescription('Remove the starboard')
|
||||||
|
.setRequired(false)),
|
||||||
|
category: 'admin',
|
||||||
|
userPermissions: [PermissionFlagsBits.ManageChannels],
|
||||||
|
async execute(interaction, args) {
|
||||||
|
if (args.remove) {
|
||||||
|
fs.unlink(`./json/board/star${interaction.guild.id}.json`, (err) => {
|
||||||
|
if (err) {return interaction.reply('There is no starboard');}
|
||||||
|
return interaction.reply('Deleted the starboard');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!args.emote || !args.count) return interaction.reply('You are missing the emote or the count arg!');
|
||||||
|
fs.writeFile(`./json/board/star${interaction.guild.id}.json`, `{"starboard": "${interaction.channel.id}", "emote": "${args.emote}", "count": "${args.count}"}`, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return interaction.reply(`This channel have been set as the starboard with ${args.emote} with the minimum of ${args.count}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
127
commands/admin/tag.js
Normal file
127
commands/admin/tag.js
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
import { SlashCommandBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder, PermissionFlagsBits, PermissionsBitField } from 'discord.js';
|
||||||
|
import os from 'node:os';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
|
||||||
|
import db from '../../models/index.js';
|
||||||
|
|
||||||
|
const { ownerId } = process.env;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('tag')
|
||||||
|
.setDescription('Create custom autoresponse')
|
||||||
|
.addStringOption(option =>
|
||||||
|
option.setName('trigger')
|
||||||
|
.setDescription('The strings that will trigger the tag')
|
||||||
|
.setRequired(false))
|
||||||
|
.addStringOption(option =>
|
||||||
|
option.setName('response')
|
||||||
|
.setDescription('What it will answer back')
|
||||||
|
.setRequired(false))
|
||||||
|
.addBooleanOption(option =>
|
||||||
|
option.setName('remove')
|
||||||
|
.setDescription('(ADMIN ONLY!) Remove the tag')
|
||||||
|
.setRequired(false))
|
||||||
|
.addBooleanOption(option =>
|
||||||
|
option.setName('list')
|
||||||
|
.setDescription('List all the tags for the server')
|
||||||
|
.setRequired(false)),
|
||||||
|
category: 'admin',
|
||||||
|
userPermissions: [PermissionFlagsBits.ManageChannels],
|
||||||
|
async execute(interaction, args, client) {
|
||||||
|
await interaction.deferReply();
|
||||||
|
|
||||||
|
if (args.list) {
|
||||||
|
let tagList = await db.Tag.findAll({ attributes: ['trigger', 'response', 'ownerID'], where: { serverID: interaction.guild.id } });
|
||||||
|
|
||||||
|
if (args.trigger) {
|
||||||
|
tagList = await db.Tag.findOne({ attributes: ['trigger', 'response', 'ownerID'], where: { trigger: args.trigger, serverID: interaction.guild.id } });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tagList) return interaction.editReply('It looks like the server has no tags.');
|
||||||
|
|
||||||
|
const path = `${os.tmpdir()}/${interaction.guild.id}.json`;
|
||||||
|
fs.writeFile(path, JSON.stringify(tagList, null, 2), function(err) {
|
||||||
|
if (err) return console.error(err);
|
||||||
|
});
|
||||||
|
return interaction.editReply({ files: [path] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const tag = await db.Tag.findOne({ where: { trigger: args.trigger, serverID: interaction.guild.id } });
|
||||||
|
|
||||||
|
if (args.remove) {
|
||||||
|
if (tag) {
|
||||||
|
if (tag.get('ownerID') == interaction.user.id || interaction.member.permissionsIn(interaction.channel).has(PermissionsBitField.Flags.Administrator) || interaction.user.id == ownerId) {
|
||||||
|
db.Tag.destroy({ where: { trigger: args.trigger, serverID: interaction.guild.id } });
|
||||||
|
return interaction.editReply('successfully deleted the following tag: ' + args.trigger);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return interaction.editReply(`You are not the owner of this tag, if you think it is problematic ask a user with the 'Administrator' permission to remove it by doing ${this.client.commandHandler.prefix[0]}tag ${args.trigger} --remove`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return interaction.editReply('Did not find the specified tag, are you sure it exist?');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!args.trigger) return interaction.editReply('You need to specify what you want me to respond to.');
|
||||||
|
if (!args.response) return interaction.editReply('You need to specify what you want me to answer with.');
|
||||||
|
|
||||||
|
if (!tag) {
|
||||||
|
const body = { trigger: args.trigger, response: args.response, ownerID: interaction.user.id, serverID: interaction.guild.id };
|
||||||
|
await db.Tag.create(body);
|
||||||
|
return interaction.editReply(`tag have been set to ${args.trigger} : ${args.response}`);
|
||||||
|
}
|
||||||
|
else if (tag.get('ownerID') == interaction.user.id || interaction.member.permissionsIn(interaction.channel).has('ADMINISTRATOR') || interaction.user.id == ownerId) {
|
||||||
|
|
||||||
|
const row = new ActionRowBuilder()
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`edit${interaction.user.id}${interaction.id}`)
|
||||||
|
.setLabel('Edit')
|
||||||
|
.setStyle(ButtonStyle.Primary),
|
||||||
|
)
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`remove${interaction.user.id}${interaction.id}`)
|
||||||
|
.setLabel('Remove')
|
||||||
|
.setStyle(ButtonStyle.Danger),
|
||||||
|
)
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`nothing${interaction.user.id}${interaction.id}`)
|
||||||
|
.setLabel('Do nothing')
|
||||||
|
.setStyle(ButtonStyle.Secondary),
|
||||||
|
);
|
||||||
|
|
||||||
|
await interaction.editReply({ content: 'This tag already exist, do you want to update it, remove it or do nothing?', components: [row], ephemeral: true });
|
||||||
|
|
||||||
|
return listenButton(client, interaction, args, interaction.user);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return interaction.editReply(`You are not the owner of this tag, if you think it is problematic ask an admin to remove it by doing ${this.client.commandHandler.prefix[0]}tag ${args.trigger} --remove`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async function listenButton(client, interaction, args, user = interaction.user, originalId = interaction.id) {
|
||||||
|
client.once('interactionCreate', async (interactionMenu) => {
|
||||||
|
if (user !== interactionMenu.user) return listenButton(client, interaction, args, user, originalId);
|
||||||
|
if (!interactionMenu.isButton()) return;
|
||||||
|
|
||||||
|
await interactionMenu.update({ components: [] });
|
||||||
|
|
||||||
|
if (interactionMenu.customId === `edit${interaction.user.id}${originalId}`) {
|
||||||
|
const body = { trigger: args.trigger, response: args.response, ownerID: interaction.user.id, serverID: interaction.guild.id };
|
||||||
|
db.Tag.update(body, { where: { serverID: interaction.guild.id } });
|
||||||
|
return interaction.editReply({ content: `The tag ${args.trigger} has been set to ${args.response}`, ephemeral: true });
|
||||||
|
}
|
||||||
|
else if (interactionMenu.customId === `remove${interaction.user.id}${originalId}`) {
|
||||||
|
db.Tag.destroy({ where: { trigger: args.trigger, serverID: interaction.guild.id } });
|
||||||
|
return interaction.editReply({ content: `The tag ${args.trigger} has been deleted`, ephemeral: true });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
75
commands/admin/welcome.js
Normal file
75
commands/admin/welcome.js
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import { SlashCommandBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder, PermissionFlagsBits } from 'discord.js';
|
||||||
|
import db from '../../models/index.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('welcome')
|
||||||
|
.setDescription('Set a join message')
|
||||||
|
.addStringOption(option =>
|
||||||
|
option.setName('message')
|
||||||
|
.setDescription('The message you want the bot to say when someone join in the current channel.')),
|
||||||
|
category: 'admin',
|
||||||
|
userPermissions: [PermissionFlagsBits.ManageChannels],
|
||||||
|
async execute(interaction, args, client) {
|
||||||
|
const join = await db.joinChannel.findOne({ where: { guildID: interaction.guild.id } });
|
||||||
|
|
||||||
|
if (!join && !args.message) {
|
||||||
|
return interaction.reply({ content: 'You need a message for me to say anything!', ephemeral: true });
|
||||||
|
}
|
||||||
|
else if (!join) {
|
||||||
|
const body = { guildID: interaction.guild.id, channelID: interaction.channel.id, message: args.message };
|
||||||
|
await db.joinChannel.create(body);
|
||||||
|
return interaction.reply({ content: `The join message have been set with ${args.message}`, ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const row = new ActionRowBuilder()
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`edit${interaction.user.id}${interaction.id}`)
|
||||||
|
.setLabel('Edit')
|
||||||
|
.setStyle(ButtonStyle.Primary),
|
||||||
|
)
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`remove${interaction.user.id}${interaction.id}`)
|
||||||
|
.setLabel('Remove')
|
||||||
|
.setStyle(ButtonStyle.Danger),
|
||||||
|
)
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`nothing${interaction.user.id}${interaction.id}`)
|
||||||
|
.setLabel('Do nothing')
|
||||||
|
.setStyle(ButtonStyle.Secondary),
|
||||||
|
);
|
||||||
|
|
||||||
|
await interaction.reply({ content: 'The server already has a message set, do you want to edit it or remove it?', components: [row], ephemeral: true });
|
||||||
|
|
||||||
|
return listenButton(client, interaction, args, interaction.user);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async function listenButton(client, interaction, args, user = interaction.user, originalId = interaction.id) {
|
||||||
|
client.once('interactionCreate', async (interactionMenu) => {
|
||||||
|
if (user !== interactionMenu.user) return listenButton(client, interaction, args, user, originalId);
|
||||||
|
if (!interactionMenu.isButton()) return;
|
||||||
|
|
||||||
|
await interactionMenu.update({ components: [] });
|
||||||
|
|
||||||
|
if (interactionMenu.customId === `edit${interaction.user.id}${originalId}`) {
|
||||||
|
if (!args.message) {
|
||||||
|
return interaction.reply({ content: 'You need to input a message for me to edit!', ephemeral: true });
|
||||||
|
}
|
||||||
|
const body = { guildID: interaction.guild.id, channelID: interaction.channel.id, message: args.message };
|
||||||
|
await db.joinChannel.update(body, { where: { guildID: interaction.guild.id } });
|
||||||
|
return interaction.editReply({ content: `The join message has been set to ${args.message}`, ephemeral: true });
|
||||||
|
}
|
||||||
|
else if (interactionMenu.customId === `remove${interaction.user.id}${originalId}`) {
|
||||||
|
db.joinChannel.destroy({ where: { guildID: interaction.guild.id, channelID: interaction.channel.id } });
|
||||||
|
return interaction.editReply({ content: 'The join message has been deleted.', ephemeral: true });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
import { SlashCommandBuilder } from '@discordjs/builders';
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
import { MessageEmbed } from 'discord.js';
|
import { EmbedBuilder } from 'discord.js';
|
||||||
import TurndownService from 'turndown';
|
import TurndownService from 'turndown';
|
||||||
const turndown = new TurndownService();
|
const turndown = new TurndownService();
|
||||||
import fetch from 'node-fetch';
|
import fetch from 'node-fetch';
|
||||||
|
|
||||||
import fourChan from '../../json/4chan.json' assert {type: 'json'};
|
import fourChan from '../../json/4chan.json' with {type: 'json'};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
|
@ -14,8 +14,9 @@ export default {
|
||||||
option.setName('board')
|
option.setName('board')
|
||||||
.setDescription('The board you wish to see')
|
.setDescription('The board you wish to see')
|
||||||
.setRequired(true)),
|
.setRequired(true)),
|
||||||
async execute(interaction) {
|
category: 'fun',
|
||||||
let board = interaction.options.getString('board');
|
async execute(interaction, args) {
|
||||||
|
let board = args.board;
|
||||||
|
|
||||||
if (fourChan[board] == undefined) {
|
if (fourChan[board] == undefined) {
|
||||||
return interaction.reply({ content: 'Uh oh! The board you are looking for does not exist? You think this is a mistake? Please send a feedback telling me so!', ephemeral: true });
|
return interaction.reply({ content: 'Uh oh! The board you are looking for does not exist? You think this is a mistake? Please send a feedback telling me so!', ephemeral: true });
|
||||||
|
@ -65,8 +66,8 @@ export default {
|
||||||
title = 'No title';
|
title = 'No title';
|
||||||
}
|
}
|
||||||
|
|
||||||
const FourchanEmbed = new MessageEmbed()
|
const FourchanEmbed = new EmbedBuilder()
|
||||||
.setColor(interaction.member ? interaction.member.displayHexColor : 'NAVY')
|
.setColor(interaction.member ? interaction.member.displayHexColor : 'Navy')
|
||||||
.setTitle(turndown.turndown(title))
|
.setTitle(turndown.turndown(title))
|
||||||
.setDescription(turndown.turndown(description))
|
.setDescription(turndown.turndown(description))
|
||||||
.setImage(`https://i.4cdn.org/${board}/${response.threads[i].posts[0].tim}${response.threads[i].posts[0].ext}`)
|
.setImage(`https://i.4cdn.org/${board}/${response.threads[i].posts[0].tim}${response.threads[i].posts[0].ext}`)
|
||||||
|
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { SlashCommandBuilder } from '@discordjs/builders';
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
import { Permissions } from 'discord.js';
|
import { PermissionFlagsBits } from 'discord.js';
|
||||||
export default {
|
export default {
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
.setName('fakeuser')
|
.setName('fakeuser')
|
||||||
|
@ -16,14 +16,18 @@ export default {
|
||||||
option.setName('image')
|
option.setName('image')
|
||||||
.setDescription('Optional attachment.')
|
.setDescription('Optional attachment.')
|
||||||
.setRequired(false)),
|
.setRequired(false)),
|
||||||
clientPermissions: [ Permissions.FLAGS.MANAGE_WEBHOOKS ],
|
category: 'fun',
|
||||||
async execute(interaction) {
|
clientPermissions: [ PermissionFlagsBits.ManageWebhooks ],
|
||||||
|
async execute(interaction, args) {
|
||||||
await interaction.deferReply({ ephemeral: true });
|
await interaction.deferReply({ ephemeral: true });
|
||||||
const attachment = interaction.options.getAttachment('image');
|
await interaction.guild.members.fetch();
|
||||||
const message = interaction.options.getString('message');
|
const member = args.user;
|
||||||
const member = interaction.options.getMentionable('user');
|
const message = args.message;
|
||||||
|
const attachment = args.image;
|
||||||
|
const username = member.nickname ? member.nickname : member.user.username;
|
||||||
|
|
||||||
const webhook = await interaction.channel.createWebhook(member.user.username, {
|
const webhook = await interaction.channel.createWebhook({
|
||||||
|
name: username,
|
||||||
avatar: member.user.displayAvatarURL(),
|
avatar: member.user.displayAvatarURL(),
|
||||||
reason: `Fakebot/user command triggered by: ${interaction.user.username}`,
|
reason: `Fakebot/user command triggered by: ${interaction.user.username}`,
|
||||||
});
|
});
|
||||||
|
@ -34,6 +38,12 @@ export default {
|
||||||
await webhook.send({ content: message });
|
await webhook.send({ content: message });
|
||||||
}
|
}
|
||||||
await webhook.delete(`Fakebot/user command triggered by: ${interaction.user.username}`);
|
await webhook.delete(`Fakebot/user command triggered by: ${interaction.user.username}`);
|
||||||
await interaction.editReply({ content: `Faked the user ${member}` });
|
if (interaction.isMessage) {
|
||||||
|
await interaction.delete();
|
||||||
|
await interaction.deleteReply();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await interaction.editReply({ content: `Faked the user ${member}` });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
93
commands/fun/guess
Normal file
93
commands/fun/guess
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
// Tried something to make it work purely with slash commands but it did not work.
|
||||||
|
// The interaction created from a modal lack the showModal function so I can't just call another modal on top
|
||||||
|
// A "solution" for this that I could see is creating a button between each response asking the player to continue or stop which would create a new interaction and (maybe) allow to display a new modal
|
||||||
|
import { SlashCommandBuilder, ActionRowBuilder, TextInputBuilder, SelectMenuBuilder, ModalBuilder, TextInputStyle, InteractionType } from 'discord.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('guess')
|
||||||
|
.setDescription('Guess the number'),
|
||||||
|
category: 'fun',
|
||||||
|
async execute(interaction, args, client) {
|
||||||
|
const row = new ActionRowBuilder()
|
||||||
|
.addComponents(
|
||||||
|
new SelectMenuBuilder()
|
||||||
|
.setCustomId('difficulty')
|
||||||
|
.setPlaceholder('Nothing selected')
|
||||||
|
.addOptions([
|
||||||
|
{ label: 'Easy', value: '100' },
|
||||||
|
{ label: 'Normal', value: '1000' },
|
||||||
|
{ label: 'Hard', value: '10000' },
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
await interaction.reply({ content: 'Which difficulty do you want to play?', ephemeral: true, components: [row] });
|
||||||
|
|
||||||
|
let numberTry = 0;
|
||||||
|
let secretnumber = 0;
|
||||||
|
|
||||||
|
client.on('interactionCreate', async (interactionMenu) => {
|
||||||
|
if (interaction.user !== interactionMenu.user) return;
|
||||||
|
|
||||||
|
const modal = new ModalBuilder()
|
||||||
|
.setCustomId('guessModal')
|
||||||
|
.setTitle('Your guess');
|
||||||
|
|
||||||
|
|
||||||
|
const textRow = new ActionRowBuilder()
|
||||||
|
.addComponents(
|
||||||
|
new TextInputBuilder()
|
||||||
|
.setCustomId('input')
|
||||||
|
.setLabel('What is the number?')
|
||||||
|
.setStyle(TextInputStyle.Short),
|
||||||
|
);
|
||||||
|
|
||||||
|
modal.addComponents(textRow);
|
||||||
|
|
||||||
|
async function tryAgain(input) {
|
||||||
|
if (input != secretnumber) {
|
||||||
|
if (input > secretnumber) {
|
||||||
|
modal.setTitle('Its less!\nWhat is the number?');
|
||||||
|
}
|
||||||
|
else if (input < secretnumber) {
|
||||||
|
modal.setTitle('Its more!\nWhat is the number?');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await interactionMenu.showModal(modal);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkNumber(input) {
|
||||||
|
numberTry++;
|
||||||
|
if (input.toLowerCase() === 'stop') {
|
||||||
|
return interaction.reply('Ok, let\'s stop playing :(');
|
||||||
|
}
|
||||||
|
else if (input != secretnumber) {
|
||||||
|
console.log('trying again');
|
||||||
|
tryAgain(input);
|
||||||
|
}
|
||||||
|
else if (numberTry > 1) {
|
||||||
|
return interaction.reply(`Congratulations! You won! It took you ${numberTry} turns!`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return interaction.reply('Congratulations! You won! It took you 1 Turn!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interactionMenu.type === InteractionType.ModalSubmit) {
|
||||||
|
if (interactionMenu.customId === 'guessModal') {
|
||||||
|
const input = interactionMenu.fields.getTextInputValue('input');
|
||||||
|
checkNumber(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (interactionMenu.isSelectMenu()) {
|
||||||
|
if (interactionMenu.customId === 'difficulty') {
|
||||||
|
secretnumber = Math.floor((Math.random() * parseInt(interactionMenu.values[0])) + 1);
|
||||||
|
console.log(secretnumber);
|
||||||
|
|
||||||
|
// await interaction.followUp({ content: 'What is the number?', ephemeral: true });
|
||||||
|
await interactionMenu.showModal(modal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
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,10 +1,12 @@
|
||||||
import { SlashCommandBuilder } from '@discordjs/builders';
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
import fetch from 'node-fetch';
|
import fetch from 'node-fetch';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
.setName('inspirobot')
|
.setName('inspirobot')
|
||||||
.setDescription('Get an image from inspirobot'),
|
.setDescription('Get an image from inspirobot'),
|
||||||
|
category: 'fun',
|
||||||
|
alias: ['ib'],
|
||||||
async execute(interaction) {
|
async execute(interaction) {
|
||||||
fetch('http://inspirobot.me/api?generate=true')
|
fetch('http://inspirobot.me/api?generate=true')
|
||||||
.then(res => res.text())
|
.then(res => res.text())
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { SlashCommandBuilder } from '@discordjs/builders';
|
import { SlashCommandBuilder, EmbedBuilder } from 'discord.js';
|
||||||
import { MessageEmbed } from 'discord.js';
|
|
||||||
import fetch from 'node-fetch';
|
import fetch from 'node-fetch';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -10,10 +9,11 @@ export default {
|
||||||
option.setName('subreddit')
|
option.setName('subreddit')
|
||||||
.setDescription('The subreddit you wish to see')
|
.setDescription('The subreddit you wish to see')
|
||||||
.setRequired(true)),
|
.setRequired(true)),
|
||||||
async execute(interaction) {
|
category: 'fun',
|
||||||
|
async execute(interaction, args) {
|
||||||
await interaction.deferReply({ ephemeral: false });
|
await interaction.deferReply({ ephemeral: false });
|
||||||
|
const subreddit = args.subreddit;
|
||||||
fetch('https://www.reddit.com/r/' + interaction.options.getString('subreddit') + '.json?limit=100').then((response) => {
|
fetch('https://www.reddit.com/r/' + subreddit + '.json?limit=100').then((response) => {
|
||||||
return response.json();
|
return response.json();
|
||||||
}).then((response) => {
|
}).then((response) => {
|
||||||
if (response.error == 404) {
|
if (response.error == 404) {
|
||||||
|
@ -27,10 +27,15 @@ export default {
|
||||||
if (response.data.children[i].data.over_18 == true && !interaction.channel.nsfw) {
|
if (response.data.children[i].data.over_18 == true && !interaction.channel.nsfw) {
|
||||||
return interaction.editReply('No nsfw');
|
return interaction.editReply('No nsfw');
|
||||||
}
|
}
|
||||||
const redditEmbed = new MessageEmbed()
|
|
||||||
.setColor(interaction.member ? interaction.member.displayHexColor : 'NAVY')
|
let description = response.data.children[i].data.selftext;
|
||||||
|
if (description === '') {
|
||||||
|
description = 'No description.';
|
||||||
|
}
|
||||||
|
const redditEmbed = new EmbedBuilder()
|
||||||
|
.setColor(interaction.member ? interaction.member.displayHexColor : 'Navy')
|
||||||
.setTitle(response.data.children[i].data.title)
|
.setTitle(response.data.children[i].data.title)
|
||||||
.setDescription(response.data.children[i].data.selftext)
|
.setDescription(description)
|
||||||
.setURL('https://reddit.com' + response.data.children[i].data.permalink)
|
.setURL('https://reddit.com' + response.data.children[i].data.permalink)
|
||||||
.setFooter({ text: `/r/${response.data.children[i].data.subreddit} | ⬆ ${response.data.children[i].data.ups} 🗨 ${response.data.children[i].data.num_comments}` });
|
.setFooter({ text: `/r/${response.data.children[i].data.subreddit} | ⬆ ${response.data.children[i].data.ups} 🗨 ${response.data.children[i].data.num_comments}` });
|
||||||
|
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
import { SlashCommandBuilder } from '@discordjs/builders';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName('s')
|
|
||||||
.setDescription('What could this be 🤫')
|
|
||||||
.addStringOption(option =>
|
|
||||||
option.setName('something')
|
|
||||||
.setDescription('🤫')
|
|
||||||
.setRequired(true)),
|
|
||||||
async execute(interaction) {
|
|
||||||
const command = interaction.options.getString('something');
|
|
||||||
|
|
||||||
if (command === 'levertowned') {
|
|
||||||
interaction.reply('Hello buddy bro <:youngtroll:488559163832795136> <@434762632004894746>');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,14 +1,14 @@
|
||||||
import { SlashCommandBuilder } from '@discordjs/builders';
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
import { MessageEmbed } from 'discord.js';
|
import { EmbedBuilder } from 'discord.js';
|
||||||
import Twit from 'twit';
|
import { TwitterApi } from 'twitter-api-v2';
|
||||||
import fetch from 'node-fetch';
|
import fetch from 'node-fetch';
|
||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
|
import util from 'node:util';
|
||||||
|
import stream from 'node:stream';
|
||||||
|
|
||||||
import db from '../../models/index.js';
|
import db from '../../models/index.js';
|
||||||
import wordToCensor from '../../json/censor.json' assert {type: 'json'};
|
import wordToCensor from '../../json/censor.json' with {type: 'json'};
|
||||||
import dotenv from 'dotenv';
|
|
||||||
dotenv.config();
|
|
||||||
const { twiConsumer, twiConsumerSecret, twiToken, twiTokenSecret, twiChannel, twiLogChannel } = process.env;
|
const { twiConsumer, twiConsumerSecret, twiToken, twiTokenSecret, twiChannel, twiLogChannel } = process.env;
|
||||||
|
|
||||||
const Blacklists = db.Blacklists;
|
const Blacklists = db.Blacklists;
|
||||||
|
@ -16,33 +16,55 @@ const Blacklists = db.Blacklists;
|
||||||
export default {
|
export default {
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
.setName('tweet')
|
.setName('tweet')
|
||||||
.setDescription('Send tweet from Haha yes twitter account. Please do not use it for advertisement and keep it english')
|
.setDescription('Send tweet from the bot twitter account. Please do not use it for advertisement and keep it english')
|
||||||
.addStringOption(option =>
|
.addStringOption(option =>
|
||||||
option.setName('content')
|
option.setName('content')
|
||||||
.setDescription('The content of the tweet you want to send me.')
|
.setDescription('!THIS IS NOT FEEDBACK! The content of the tweet you want to send me.')
|
||||||
.setRequired(false))
|
.setRequired(false))
|
||||||
.addAttachmentOption(option =>
|
.addAttachmentOption(option =>
|
||||||
option.setName('image')
|
option.setName('image')
|
||||||
.setDescription('Optional attachment (Image only.)')
|
.setDescription('Optional attachment (Image only.)')
|
||||||
.setRequired(false)),
|
.setRequired(false)),
|
||||||
|
category: 'fun',
|
||||||
ratelimit: 3,
|
ratelimit: 3,
|
||||||
cooldown: 3600,
|
cooldown: 86400,
|
||||||
async execute(interaction) {
|
guildOnly: true,
|
||||||
if (!interaction.options.getString('content') && !interaction.options.getAttachment('image')) {
|
async execute(interaction, args, client) {
|
||||||
|
const content = args.content;
|
||||||
|
const attachment = args.image;
|
||||||
|
|
||||||
|
if (!content && !attachment) {
|
||||||
return interaction.reply({ content: 'Uh oh! You are missing any content for me to tweet!', ephemeral: true });
|
return interaction.reply({ content: 'Uh oh! You are missing any content for me to tweet!', ephemeral: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
await interaction.deferReply({ ephemeral: false });
|
await interaction.deferReply({ ephemeral: false });
|
||||||
let tweet = interaction.options.getString('content');
|
let tweet = content;
|
||||||
const attachment = interaction.options.getAttachment('image');
|
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
|
|
||||||
|
// If guild is less than 1 month old don't accept the tweet
|
||||||
|
if (interaction.guild.createdAt > date.setMonth(date.getMonth() - 1)) {
|
||||||
|
await interaction.editReply({ content: 'The server need to be 1 month old to be able to use this command!' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the date for the next check
|
||||||
|
date.setTime(Date.now());
|
||||||
|
|
||||||
|
// If the bot has been in the guild for less than 1 week don't accept the tweet.
|
||||||
|
if (interaction.guild.createdAt > date.setDate(date.getDate() - 7)) {
|
||||||
|
await interaction.editReply({ content: 'I need to be in this server for a week to be able to use this command!' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the date for the next check
|
||||||
|
date.setTime(Date.now());
|
||||||
|
|
||||||
// If account is less than 6 months old don't accept the tweet ( alt prevention )
|
// If account is less than 6 months old don't accept the tweet ( alt prevention )
|
||||||
if (interaction.user.createdAt > date.setMonth(date.getMonth() - 6)) {
|
if (interaction.user.createdAt > date.setMonth(date.getMonth() - 6)) {
|
||||||
await interaction.editReply({ content: 'Your account is too new to be able to use this command!' });
|
await interaction.editReply({ content: 'Your account is too new to be able to use this command!' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the current date so it checks correctly for the 1 year requirement.
|
// Reset the date for the next check
|
||||||
date.setTime(Date.now());
|
date.setTime(Date.now());
|
||||||
|
|
||||||
// If account is less than 1 year old don't accept attachment
|
// If account is less than 1 year old don't accept attachment
|
||||||
|
@ -51,20 +73,33 @@ export default {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove zero width space
|
|
||||||
if (tweet) {
|
if (tweet) {
|
||||||
|
// remove zero width space
|
||||||
tweet = tweet.replace('', '');
|
tweet = tweet.replace('', '');
|
||||||
}
|
// This should only happen if someone tweets a zero width space
|
||||||
|
if (tweet.length === 0) {
|
||||||
|
return interaction.reply({ content: 'Uh oh! You are missing any content for me to tweet!', ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
if (tweet) {
|
wordToCensor.forEach(async word => {
|
||||||
|
if (tweet.toLowerCase().includes(word.toLowerCase())) {
|
||||||
|
const body = { type:'tweet', uid: interaction.user.id, reason: 'Automatic ban from banned word.' };
|
||||||
|
Blacklists.create(body);
|
||||||
|
|
||||||
|
await interaction.editReply({ content: 'Sike, you just posted cringe! Enjoy the blacklist :)' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
// Detect banned word (Blacklist the user directly)
|
// Detect banned word (Blacklist the user directly)
|
||||||
if (wordToCensor.includes(tweet) || wordToCensor.includes(tweet.substr(0, tweet.length - 1)) || wordToCensor.includes(tweet.substr(1, tweet.length))) {
|
/* 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.' };
|
const body = { type:'tweet', uid: interaction.user.id, reason: 'Automatic ban from banned word.' };
|
||||||
Blacklists.create(body);
|
Blacklists.create(body);
|
||||||
|
|
||||||
await interaction.editReply({ content: 'Sike, you just posted cringe! Enjoy the blacklist :)' });
|
await interaction.editReply({ content: 'Sike, you just posted cringe! Enjoy the blacklist :)' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// Very simple link detection
|
// Very simple link detection
|
||||||
if (new RegExp('([a-zA-Z0-9]+://)?([a-zA-Z0-9_]+:[a-zA-Z0-9_]+@)?([a-zA-Z0-9.-]+\\.[A-Za-z]{2,4})(:[0-9]+)?(/.*)?').test(tweet) && !tweet.includes('twitter.com')) {
|
if (new RegExp('([a-zA-Z0-9]+://)?([a-zA-Z0-9_]+:[a-zA-Z0-9_]+@)?([a-zA-Z0-9.-]+\\.[A-Za-z]{2,4})(:[0-9]+)?(/.*)?').test(tweet) && !tweet.includes('twitter.com')) {
|
||||||
|
@ -78,45 +113,35 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const T = new Twit({
|
|
||||||
consumer_key: twiConsumer,
|
const userClient = new TwitterApi({
|
||||||
consumer_secret: twiConsumerSecret,
|
appKey: twiConsumer,
|
||||||
access_token: twiToken,
|
appSecret: twiConsumerSecret,
|
||||||
access_token_secret: twiTokenSecret,
|
accessToken: twiToken,
|
||||||
|
accessSecret: twiTokenSecret,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Make sure there is an attachment and if its an image
|
// Make sure there is an attachment and if its an image
|
||||||
if (attachment) {
|
if (attachment) {
|
||||||
if (attachment.name.toLowerCase().endsWith('.jpg') || attachment.name.toLowerCase().endsWith('.png') || attachment.name.toLowerCase().endsWith('.gif')) {
|
if (attachment.name.toLowerCase().endsWith('.jpg') || attachment.name.toLowerCase().endsWith('.png') || attachment.name.toLowerCase().endsWith('.gif')) {
|
||||||
fetch(attachment.url)
|
const streamPipeline = util.promisify(stream.pipeline);
|
||||||
.then(res => {
|
const res = await fetch(attachment.url);
|
||||||
const dest = fs.createWriteStream(`${os.tmpdir()}/${attachment.name}`);
|
if (!res.ok) return interaction.editReply('An error has occured while trying to download your image.');
|
||||||
res.body.pipe(dest);
|
await streamPipeline(res.body, fs.createWriteStream(`${os.tmpdir()}/${attachment.name}`));
|
||||||
dest.on('finish', () => {
|
|
||||||
const file = fs.statSync(`${os.tmpdir()}/${attachment.name}`);
|
|
||||||
const fileSize = file.size / 1000000.0;
|
|
||||||
|
|
||||||
if ((attachment.name.toLowerCase().endsWith('.jpg') || attachment.name.toLowerCase().endsWith('.png')) && fileSize > 5) {
|
const file = fs.statSync(`${os.tmpdir()}/${attachment.name}`);
|
||||||
return interaction.editReply({ content: 'Images can\'t be larger than 5 MB!' });
|
const fileSize = file.size / 1000000.0;
|
||||||
}
|
|
||||||
else if (attachment.name.toLowerCase().endsWith('.gif') && fileSize > 15) {
|
|
||||||
return interaction.editReply({ content: 'Gifs can\'t be larger than 15 MB!' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const b64Image = fs.readFileSync(`${os.tmpdir()}/${attachment.name}`, { encoding: 'base64' });
|
if ((attachment.name.toLowerCase().endsWith('.jpg') || attachment.name.toLowerCase().endsWith('.png')) && fileSize > 5) {
|
||||||
T.post('media/upload', { media_data: b64Image }, function(err, data) {
|
return interaction.editReply({ content: 'Images can\'t be larger than 5 MB!' });
|
||||||
if (err) {
|
}
|
||||||
console.log('OH NO AN ERROR!!!!!!!');
|
else if (attachment.name.toLowerCase().endsWith('.gif') && fileSize > 15) {
|
||||||
console.error(err);
|
return interaction.editReply({ content: 'Gifs can\'t be larger than 15 MB!' });
|
||||||
return interaction.editReply({ content: 'OH NO!!! AN ERROR HAS occurred!!! please hold on while i find what\'s causing this issue! ' });
|
}
|
||||||
}
|
|
||||||
else {
|
const image = await userClient.v1.uploadMedia(`${os.tmpdir()}/${attachment.name}`);
|
||||||
Tweet(data);
|
Tweet(image);
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
await interaction.editReply({ content: 'File type not supported, you can only send jpg/png/gif' });
|
await interaction.editReply({ content: 'File type not supported, you can only send jpg/png/gif' });
|
||||||
|
@ -133,70 +158,47 @@ export default {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Tweet(data) {
|
async function Tweet(img) {
|
||||||
let options = {
|
let options = null;
|
||||||
status: tweet,
|
if (img) {
|
||||||
};
|
options = { media: { media_ids: new Array(img) } };
|
||||||
|
|
||||||
if (data && tweet) {
|
|
||||||
options = {
|
|
||||||
status: tweet,
|
|
||||||
media_ids: new Array(data.media_id_string),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
else if (data) {
|
const tweeted = await userClient.v2.tweet(tweet, options);
|
||||||
options = {
|
|
||||||
media_ids: new Array(data.media_id_string),
|
const tweetid = tweeted.data.id;
|
||||||
};
|
const FunnyWords = ['oppaGangnamStyle', '69', '420', 'cum', 'funnyMan', 'GUCCISmartToilet', 'TwitterForClowns', 'fart', 'ok', 'hi', 'howAreYou', 'WhatsNinePlusTen', '21'];
|
||||||
|
const TweetLink = `https://vxtwitter.com/${FunnyWords[Math.floor((Math.random() * FunnyWords.length))]}/status/${tweetid}`;
|
||||||
|
|
||||||
|
let channel = await client.channels.resolve(twiChannel);
|
||||||
|
channel.send(TweetLink);
|
||||||
|
|
||||||
|
const Embed = new EmbedBuilder()
|
||||||
|
.setAuthor({ name: interaction.user.username, iconURL: interaction.user.displayAvatarURL() })
|
||||||
|
.setDescription(tweet ? tweet : 'No content.')
|
||||||
|
.addFields(
|
||||||
|
{ name: 'Link', value: TweetLink, inline: true },
|
||||||
|
{ name: 'Tweet ID', value: tweetid, inline: true },
|
||||||
|
{ name: 'Channel ID', value: interaction.channel.id, inline: true },
|
||||||
|
{ name: 'Message ID', value: interaction.id, inline: true },
|
||||||
|
{ name: 'Author', value: `${interaction.user.username} (${interaction.user.id})`, inline: true },
|
||||||
|
)
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
if (interaction.guild) {
|
||||||
|
Embed.addFields(
|
||||||
|
{ name: 'Guild', value: `${interaction.guild.name} (${interaction.guild.id})`, inline: true },
|
||||||
|
{ name: 'message link', value: `https://discord.com/channels/${interaction.guild.id}/${interaction.channel.id}/${interaction.id}`, inline: true },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Embed.addFields({ name: 'message link', value: `https://discord.com/channels/@me/${interaction.channel.id}/${interaction.id}` });
|
||||||
}
|
}
|
||||||
|
|
||||||
T.post('statuses/update', options, function(err, response) {
|
if (attachment) Embed.setImage(attachment.url);
|
||||||
if (err) {
|
|
||||||
// Rate limit exceeded
|
|
||||||
if (err.code == 88) return interaction.editReply({ content: err.interaction });
|
|
||||||
// Tweet needs to be a bit shorter.
|
|
||||||
if (err.code == 186) return interaction.editReply({ content: `${err.interaction} Your interaction was ${tweet.length} characters, you need to remove ${tweet.length - 280} characters (This count may be inaccurate if your interaction contained link)` });
|
|
||||||
// Status is a duplicate.
|
|
||||||
if (err.code == 187) return interaction.editReply({ content: err.interaction });
|
|
||||||
// To protect our users from spam and other malicious activity, this account is temporarily locked.
|
|
||||||
if (err.code == 326) return interaction.editReply({ content: err.interaction });
|
|
||||||
console.error('OH NO!!!!');
|
|
||||||
console.error(err);
|
|
||||||
return interaction.editReply({ content: 'OH NO!!! AN ERROR HAS occurred!!! please hold on while i find what\'s causing this issue!' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const tweetid = response.id_str;
|
channel = await client.channels.resolve(twiLogChannel);
|
||||||
const FunnyWords = ['oppaGangnamStyle', '69', '420', 'cum', 'funnyMan', 'GUCCISmartToilet', 'TwitterForClowns', 'fart', 'mcDotnamejeffDotxyz', 'ok', 'hi', 'howAreYou', 'WhatsNinePlusTen', '21'];
|
channel.send({ embeds: [Embed] });
|
||||||
const TweetLink = `https://twitter.com/${FunnyWords[Math.floor((Math.random() * FunnyWords.length))]}/status/${tweetid}`;
|
return interaction.editReply({ content: `Go see ur epic tweet ${TweetLink}` });
|
||||||
|
|
||||||
// Im too lazy for now to make an entry in config.json
|
|
||||||
let channel = interaction.client.channels.resolve(twiChannel);
|
|
||||||
channel.send(TweetLink);
|
|
||||||
|
|
||||||
const Embed = new MessageEmbed()
|
|
||||||
.setAuthor({ name: interaction.user.username, iconURL: interaction.user.displayAvatarURL() })
|
|
||||||
.setDescription(tweet)
|
|
||||||
.addField('Link', TweetLink, true)
|
|
||||||
.addField('Tweet ID', tweetid, true)
|
|
||||||
.addField('Channel ID', interaction.channel.id, true)
|
|
||||||
.addField('Messsage ID', interaction.id, true)
|
|
||||||
.addField('Author', `${interaction.user.username} (${interaction.user.id})`, true)
|
|
||||||
.setTimestamp();
|
|
||||||
|
|
||||||
if (interaction.guild) {
|
|
||||||
Embed.addField('Guild', `${interaction.guild.name} (${interaction.guild.id})`, true);
|
|
||||||
Embed.addField('message link', `https://discord.com/channels/${interaction.guild.id}/${interaction.channel.id}/${interaction.id}`);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Embed.addField('message link', `https://discord.com/channels/@me/${interaction.channel.id}/${interaction.id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attachment) Embed.setImage(attachment.url);
|
|
||||||
|
|
||||||
channel = interaction.client.channels.resolve(twiLogChannel);
|
|
||||||
channel.send({ embeds: [Embed] });
|
|
||||||
return interaction.editReply({ content: `Go see ur epic tweet ${TweetLink}` });
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
90
commands/fun/ytp.js
Normal file
90
commands/fun/ytp.js
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import os from 'node:os';
|
||||||
|
import YTPGenerator from 'ytpplus-node';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('ytp')
|
||||||
|
.setDescription('Generate a YTP')
|
||||||
|
.addBooleanOption(option =>
|
||||||
|
option.setName('force')
|
||||||
|
.setDescription('Force the generation of the video in non-nsfw channel.')
|
||||||
|
.setRequired(false)),
|
||||||
|
category: 'fun',
|
||||||
|
ratelimit: 2,
|
||||||
|
cooldown: 60,
|
||||||
|
parallelLimit: 30,
|
||||||
|
async execute(interaction, args) {
|
||||||
|
if (!interaction.channel.nsfw && !args.force) return interaction.reply(`Please execute this command in an NSFW channel ( Content might not be NSFW but since the video are user submitted better safe than sorry ) OR do \`\`${interaction.prefix}ytp --force\`\` to make the command work outside of nsfw channel BE AWARE THAT IT WON'T CHANGE THE FINAL RESULT SO NSFW CAN STILL HAPPEN`);
|
||||||
|
|
||||||
|
// Read userVid folder and select random vid and only take .mp4
|
||||||
|
const mp4 = [];
|
||||||
|
const asset = [];
|
||||||
|
// Count number of total vid
|
||||||
|
fs.readdirSync('./asset/ytp/userVid/').forEach(file => {
|
||||||
|
if (file.endsWith('mp4')) {
|
||||||
|
mp4.push(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const MAX_CLIPS = 20;
|
||||||
|
// Select random vid depending on the amount of MAX_CLIPS
|
||||||
|
for (let i = 0; i < MAX_CLIPS; i++) {
|
||||||
|
const random = Math.floor(Math.random() * mp4.length);
|
||||||
|
const vid = `./asset/ytp/userVid/${mp4[random]}`;
|
||||||
|
if (mp4[random].endsWith('mp4')) {
|
||||||
|
if (!asset.includes(vid)) {
|
||||||
|
asset.push(vid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadingmsg = await interaction.reply(`Processing, this can take a ***long*** time, i'll ping you when I finished <a:loadingmin:527579785212329984>\nSome info: There are currently ${mp4.length} videos, why not add yours? You can do so with the \`\`addytp\`\` command.\nLike ytp? Why not check out https://ytp.namejeff.xyz/`);
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
debug: false,
|
||||||
|
MAX_STREAM_DURATION: Math.floor((Math.random() * 3) + 1),
|
||||||
|
sources: './asset/ytp/sources/',
|
||||||
|
sounds: './asset/ytp/sounds/',
|
||||||
|
music: './asset/ytp/music/',
|
||||||
|
resources: './asset/ytp/resources/',
|
||||||
|
temp: os.tmpdir(),
|
||||||
|
sourceList: asset,
|
||||||
|
intro: args.force ? './asset/ytp/intro.mp4' : null,
|
||||||
|
outro: './asset/ytp/outro.mp4',
|
||||||
|
OUTPUT_FILE: `${os.tmpdir()}/${interaction.id}_YTP.mp4`,
|
||||||
|
MAX_CLIPS: MAX_CLIPS,
|
||||||
|
transitions: true,
|
||||||
|
showFileNames: true,
|
||||||
|
effects: {
|
||||||
|
effect_RandomSound: true,
|
||||||
|
effect_RandomSoundMute: true,
|
||||||
|
effect_Reverse: true,
|
||||||
|
effect_Chorus: true,
|
||||||
|
effect_Vibrato: true,
|
||||||
|
effect_HighPitch: true,
|
||||||
|
effect_LowPitch: true,
|
||||||
|
effect_SpeedUp: true,
|
||||||
|
effect_SlowDown: true,
|
||||||
|
effect_Dance: true,
|
||||||
|
effect_Squidward: true,
|
||||||
|
effect_How: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await new YTPGenerator().configurateAndGo(options)
|
||||||
|
.then(() => {
|
||||||
|
loadingmsg.delete();
|
||||||
|
return interaction.followUp({ content: 'Here is your YTP! Remember, it might contain nsfw, so be careful!', files: [`${os.tmpdir()}/${interaction.id}_YTP.mp4`] })
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
return interaction.followUp({ files: [`./asset/ytp/error${Math.floor(Math.random() * 2) + 1}.mp4`] });
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
loadingmsg.delete();
|
||||||
|
return interaction.followUp({ files: [`./asset/ytp/error${Math.floor(Math.random() * 2) + 1}.mp4`] });
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
|
@ -1,20 +1,21 @@
|
||||||
import { SlashCommandBuilder } from '@discordjs/builders';
|
// TODO
|
||||||
|
// Switch to 'twitter-api-v2'
|
||||||
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
import Twit from 'twit';
|
import Twit from 'twit';
|
||||||
|
|
||||||
import dotenv from 'dotenv';
|
|
||||||
dotenv.config();
|
|
||||||
const { twiConsumer, twiConsumerSecret, twiToken, twiTokenSecret } = process.env;
|
const { twiConsumer, twiConsumerSecret, twiToken, twiTokenSecret } = process.env;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
.setName('deletewteet')
|
.setName('deletetweet')
|
||||||
.setDescription('Delete a tweet')
|
.setDescription('Delete a tweet')
|
||||||
.addStringOption(option =>
|
.addStringOption(option =>
|
||||||
option.setName('tweetid')
|
option.setName('tweetid')
|
||||||
.setDescription('The id of the tweet you wish to delete.')
|
.setDescription('The id of the tweet you wish to delete.')
|
||||||
.setRequired(true)),
|
.setRequired(true)),
|
||||||
|
category: 'owner',
|
||||||
ownerOnly: true,
|
ownerOnly: true,
|
||||||
async execute(interaction) {
|
async execute(interaction, args) {
|
||||||
await interaction.deferReply();
|
await interaction.deferReply();
|
||||||
try {
|
try {
|
||||||
const T = new Twit({
|
const T = new Twit({
|
||||||
|
@ -25,7 +26,7 @@ export default {
|
||||||
});
|
});
|
||||||
|
|
||||||
T.post('statuses/destroy', {
|
T.post('statuses/destroy', {
|
||||||
id: interaction.options.getString('tweetid'),
|
id: args.tweetid,
|
||||||
});
|
});
|
||||||
return interaction.editReply('Tweet have been deleted!');
|
return interaction.editReply('Tweet have been deleted!');
|
||||||
}
|
}
|
|
@ -1,9 +1,10 @@
|
||||||
import { SlashCommandBuilder } from '@discordjs/builders';
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
.setName('die')
|
.setName('die')
|
||||||
.setDescription('Kill the bot'),
|
.setDescription('Kill the bot'),
|
||||||
|
category: 'owner',
|
||||||
ownerOnly: true,
|
ownerOnly: true,
|
||||||
async execute(interaction) {
|
async execute(interaction) {
|
||||||
console.log('\x1b[31m\x1b[47m\x1b[5mSHUTING DOWN!!!!!\x1b[0m');
|
console.log('\x1b[31m\x1b[47m\x1b[5mSHUTING DOWN!!!!!\x1b[0m');
|
||||||
|
|
69
commands/owner/dm.js
Normal file
69
commands/owner/dm.js
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import { SlashCommandBuilder, EmbedBuilder } from 'discord.js';
|
||||||
|
// const feedbackID = [];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('dm')
|
||||||
|
.setDescription('Replies with Pong!')
|
||||||
|
.addStringOption(option =>
|
||||||
|
option.setName('userid')
|
||||||
|
.setDescription('The user to who you want to send the message to.')
|
||||||
|
.setRequired(true))
|
||||||
|
.addStringOption(option =>
|
||||||
|
option.setName('message')
|
||||||
|
.setDescription('What do you want to tell them?')
|
||||||
|
.setRequired(true))
|
||||||
|
.addAttachmentOption(option =>
|
||||||
|
option.setName('image')
|
||||||
|
.setDescription('Optional attachment.')
|
||||||
|
.setRequired(false)),
|
||||||
|
category: 'owner',
|
||||||
|
ownerOnly: true,
|
||||||
|
async execute(interaction, args, client) {
|
||||||
|
/* Too lazy to implement that now (Watch it rest untouched for months)
|
||||||
|
async function uuidv4() {
|
||||||
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||||
|
const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const uuid = uuidv4();
|
||||||
|
feedbackID[uuid] = args.message;
|
||||||
|
*/
|
||||||
|
await client.users.fetch(args.userid);
|
||||||
|
const user = client.users.resolve(args.userid);
|
||||||
|
if (!user) return interaction.reply('Not a valid ID');
|
||||||
|
const text = args.message;
|
||||||
|
|
||||||
|
const Embed = new EmbedBuilder()
|
||||||
|
.setTitle('You received a message from the developer!')
|
||||||
|
.setDescription(text)
|
||||||
|
.setFooter({ text: `If you wish to respond use the following command: ${interaction.prefix}feedback <message>` })
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
user.send({ embeds: [Embed] });
|
||||||
|
return interaction.reply({ content: `DM sent to ${user.username} (${user.id})` });
|
||||||
|
/*
|
||||||
|
const Attachment = (message.attachments).array();
|
||||||
|
if (Attachment[0]) {
|
||||||
|
client.users.resolve(user).send(Embed, { files: [Attachment[0].url] })
|
||||||
|
.then(() => {
|
||||||
|
return interaction.reply(`DM sent to ${user.username}`);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
return interaction.reply(`Could not send a DM to ${user.username}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
client.users.resolve(user).send(Embed)
|
||||||
|
.then(() => {
|
||||||
|
return interaction.reply(`DM sent to ${user.username}`);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
return interaction.reply(`Could not send a DM to ${user.username}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
},
|
||||||
|
};
|
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.`);
|
||||||
|
},
|
||||||
|
};
|
22
commands/owner/load.js
Normal file
22
commands/owner/load.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('load')
|
||||||
|
.setDescription('load a command.')
|
||||||
|
.addStringOption(option =>
|
||||||
|
option.setName('file')
|
||||||
|
.setDescription('File location of the command.')
|
||||||
|
.setRequired(true)),
|
||||||
|
category: 'owner',
|
||||||
|
ownerOnly: true,
|
||||||
|
async execute(interaction, args, client) {
|
||||||
|
await interaction.deferReply();
|
||||||
|
|
||||||
|
let command = await import(`../../${args.file}`);
|
||||||
|
command = command.default;
|
||||||
|
|
||||||
|
client.commands.set(command.data.name, command);
|
||||||
|
return await interaction.editReply(`${command.data.name} has been loaded.`);
|
||||||
|
},
|
||||||
|
};
|
|
@ -1,5 +1,4 @@
|
||||||
import { SlashCommandBuilder } from '@discordjs/builders';
|
import { ButtonStyle, SlashCommandBuilder, ButtonBuilder, ActionRowBuilder } from 'discord.js';
|
||||||
import { MessageButton, MessageActionRow } from 'discord.js';
|
|
||||||
import db from '../../models/index.js';
|
import db from '../../models/index.js';
|
||||||
const Blacklists = db.Blacklists;
|
const Blacklists = db.Blacklists;
|
||||||
|
|
||||||
|
@ -19,52 +18,72 @@ export default {
|
||||||
option.setName('reason')
|
option.setName('reason')
|
||||||
.setDescription('The reason of the blacklist.')
|
.setDescription('The reason of the blacklist.')
|
||||||
.setRequired(false)),
|
.setRequired(false)),
|
||||||
|
category: 'owner',
|
||||||
ownerOnly: true,
|
ownerOnly: true,
|
||||||
async execute(interaction) {
|
async execute(interaction, args) {
|
||||||
await interaction.deferReply({ ephemeral: true });
|
await interaction.deferReply({ ephemeral: true });
|
||||||
const client = interaction.client;
|
const client = interaction.client;
|
||||||
const command = interaction.options.getString('command');
|
const command = args.command;
|
||||||
const userid = interaction.options.getString('userid');
|
const userid = args.userid;
|
||||||
const reason = interaction.options.getString('reason');
|
const reason = args.reason ? args.reason : 'No reason has been specified.';
|
||||||
|
|
||||||
const blacklist = await Blacklists.findOne({ where: { type:command, uid:userid } });
|
const blacklist = await Blacklists.findOne({ where: { type:command, uid:userid } });
|
||||||
|
|
||||||
if (!blacklist) {
|
if (!blacklist) {
|
||||||
const body = { type:command, uid: userid, reason: reason };
|
const body = { type:command, uid: userid, reason: reason };
|
||||||
Blacklists.create(body);
|
Blacklists.create(body);
|
||||||
let user = userid;
|
if (command === 'guild') {
|
||||||
if (command !== 'guild') {user = client.users.resolve(userid).tag;}
|
const guildid = userid;
|
||||||
|
await client.guilds.fetch(guildid);
|
||||||
|
const guild = client.guilds.resolve(guildid).name;
|
||||||
|
|
||||||
return interaction.editReply(`${user} has been blacklisted from ${command} with the following reason ${reason}`);
|
return interaction.editReply(`The guild ${guild} (${guildid}) has been blacklisted with the following reason \`${reason}\``);
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let user = userid;
|
||||||
|
await client.users.fetch(userid);
|
||||||
|
user = client.users.resolve(userid).username;
|
||||||
|
|
||||||
|
|
||||||
|
return interaction.editReply(`${user} (${userid}) has been blacklisted from ${command} with the following reason \`${reason}\``);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const row = new MessageActionRow()
|
const row = new ActionRowBuilder()
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new MessageButton()
|
new ButtonBuilder()
|
||||||
.setCustomId('yes')
|
.setCustomId(`yes${interaction.user.id}${interaction.id}`)
|
||||||
.setLabel('Yes')
|
.setLabel('Yes')
|
||||||
.setStyle('PRIMARY'),
|
.setStyle(ButtonStyle.Primary),
|
||||||
)
|
)
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new MessageButton()
|
new ButtonBuilder()
|
||||||
.setCustomId('no')
|
.setCustomId(`no${interaction.user.id}${interaction.id}`)
|
||||||
.setLabel('No')
|
.setLabel('No')
|
||||||
.setStyle('DANGER'),
|
.setStyle(ButtonStyle.Danger),
|
||||||
);
|
);
|
||||||
|
|
||||||
await interaction.editReply({ content: 'This user is already blacklisted, do you want to unblacklist him?', ephemeral: true, components: [row] });
|
await interaction.editReply({ content: 'This user is already blacklisted, do you want to unblacklist him?', ephemeral: true, components: [row] });
|
||||||
|
|
||||||
interaction.client.once('interactionCreate', async (interactionMenu) => {
|
return listenButton(client, interaction, command, userid, interaction.user);
|
||||||
if (!interactionMenu.isButton) return;
|
|
||||||
interactionMenu.update({ components: [] });
|
|
||||||
if (interactionMenu.customId === 'yes') {
|
|
||||||
Blacklists.destroy({ where: { type:command, uid:userid } });
|
|
||||||
return interaction.editReply(`The following ID have been unblacklisted from ${command}: ${userid}`);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return interaction.editReply('No one has been unblacklisted.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function listenButton(client, interaction, command, userid, user = interaction.user, originalId = interaction.id) {
|
||||||
|
client.once('interactionCreate', async (interactionMenu) => {
|
||||||
|
if (user !== interactionMenu.user) return listenButton(client, interaction, command, userid, user, originalId);
|
||||||
|
if (!interactionMenu.isButton()) return;
|
||||||
|
|
||||||
|
await interactionMenu.update({ components: [] });
|
||||||
|
|
||||||
|
if (interactionMenu.customId === `yes${interaction.user.id}${originalId}`) {
|
||||||
|
Blacklists.destroy({ where: { type:command, uid:userid } });
|
||||||
|
return interaction.editReply(`The following ID have been unblacklisted from ${command}: ${userid}`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return interaction.editReply('No one has been unblacklisted.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
50
commands/owner/unload.js
Normal file
50
commands/owner/unload.js
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('unload')
|
||||||
|
.setDescription('Unload a command and replace it with a placeholder')
|
||||||
|
.addStringOption(option =>
|
||||||
|
option.setName('commandname')
|
||||||
|
.setDescription('The command to unload.')
|
||||||
|
.setRequired(true))
|
||||||
|
.addStringOption(option =>
|
||||||
|
option.setName('placeholder')
|
||||||
|
.setDescription('The placeholder message you want for the command.'))
|
||||||
|
.addBooleanOption(option =>
|
||||||
|
option.setName('nofile')
|
||||||
|
.setDescription('Don\'t create the placeholder file')),
|
||||||
|
category: 'owner',
|
||||||
|
ownerOnly: true,
|
||||||
|
async execute(interaction, args, client) {
|
||||||
|
await interaction.deferReply();
|
||||||
|
if (!client.commands.has(args.commandname)) return await interaction.editReply('Command not found.');
|
||||||
|
if (!args.placeholder) args.placeholder = 'This command is unloaded, please check back later.';
|
||||||
|
|
||||||
|
if (!args.nofile) {
|
||||||
|
fs.writeFileSync(`./unloaded/${args.commandname}.js`, `
|
||||||
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: ${JSON.stringify(client.commands.get(args.commandname).data)},
|
||||||
|
category: '${client.commands.get(args.commandname).category}',
|
||||||
|
async execute(interaction) {
|
||||||
|
return interaction.reply('${args.placeholder.replace(/'/g, '\\\'')}');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
client.commands.delete(args.commandname);
|
||||||
|
if (!args.nofile) {
|
||||||
|
let command = await import(`../../unloaded/${args.commandname}.js`);
|
||||||
|
command = command.default;
|
||||||
|
|
||||||
|
client.commands.set(args.commandname, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await interaction.editReply(`${args.commandname} has been unloaded.`);
|
||||||
|
},
|
||||||
|
};
|
90
commands/secret/s.js
Normal file
90
commands/secret/s.js
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
/* eslint-disable no-case-declarations */
|
||||||
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
|
const { ownerId } = process.env;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('s')
|
||||||
|
.setDescription('What could this be 🤫')
|
||||||
|
.addStringOption(option =>
|
||||||
|
option.setName('something')
|
||||||
|
.setDescription('🤫')
|
||||||
|
.setRequired(true))
|
||||||
|
.addStringOption(option =>
|
||||||
|
option.setName('somethingelse')
|
||||||
|
.setDescription('🤫')
|
||||||
|
.setRequired(false)),
|
||||||
|
category: 'secret',
|
||||||
|
async execute(interaction, args, client) {
|
||||||
|
const command = args.something;
|
||||||
|
|
||||||
|
switch (command) {
|
||||||
|
case 'levertowned':
|
||||||
|
return interaction.reply('Hello buddy bro <:youngtroll:488559163832795136> <@434762632004894746>');
|
||||||
|
case 'owned':
|
||||||
|
const epicMessage = ['You cheated not ONLY the GAME, BUT yourself. You didn\'t GROW. You didn\'t IMPROVE. You TOOK a SHORTCUT and gained NOTHING. You EXPERIENCED a HOLLOW victory. NOTHING WAS risked and NOTHING WAS gained. It\'s SAD that you don\'t KNOW the DIFFERENCE.', 'TROLOLOLO OWNED EPIC STYLE', 'Owned noob', 'HAHA BRO YOU JUST GOT OOOOOOOOWNED HAHAHAHAHHAHA NOOOB NOOOOB NOOOB OWNED NOOB <:youngtroll:488559163832795136>', '<a:op:516341492982218756> op op op owned epic style <a:op:516341492982218756>', 'HAHAHA BRO YOU HAVE BEEN OWNED TROLL BRO STYLE', 'OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED YOU JUST GOT OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED YOU JUST GOT OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED YOU JUST GOT OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED OWNED YOU JUST GOT OWNED', 'OWNED\nFUCKING OWNED', 'OWNED!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!', 'You just got owned <a:troll:525724709833277442>', 'you just been....epicly trolled <:trole:241942993022615552>', 'I hope you have a nice day, just a simple reminder that I love you and I think you\'re pretty epic!!!!'];
|
||||||
|
|
||||||
|
const ownedMessage = epicMessage[Math.floor(Math.random() * epicMessage.length)];
|
||||||
|
|
||||||
|
await interaction.guild.members.fetch();
|
||||||
|
let owned = args.somethingelse ? interaction.guild.members.cache.find(u => u.user.username.toLowerCase().includes(args.somethingelse.toLowerCase())) : null;
|
||||||
|
if (interaction.isMessage) {
|
||||||
|
if (interaction.mentions.members.first()) {
|
||||||
|
console.log('test');
|
||||||
|
owned = interaction.mentions.members.first();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.somethingelse) {
|
||||||
|
|
||||||
|
if (owned.id === client.user.id) {
|
||||||
|
return interaction.reply('You really thought you could own me?, pathetic...');
|
||||||
|
}
|
||||||
|
else if (owned.id === ownerId) {
|
||||||
|
return interaction.reply('You really thought you could own him?, pathetic...');
|
||||||
|
}
|
||||||
|
else if (owned.id === '286054184623538177' || owned.id === '172112210863194113') {
|
||||||
|
owned = interaction.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (ownedMessage === epicMessage[0]) {
|
||||||
|
return interaction.reply(ownedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return interaction.reply(`${owned}, ${ownedMessage}`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return interaction.reply(ownedMessage);
|
||||||
|
}
|
||||||
|
case 'fartpiss':
|
||||||
|
if (interaction.guild.id != '240843640375607296') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await interaction.guild.members.fetch();
|
||||||
|
const member = args.somethingelse ? interaction.guild.members.cache.find(u => u.user.username.toLowerCase().includes(args.somethingelse.toLowerCase())) : null;
|
||||||
|
|
||||||
|
if (member) {
|
||||||
|
return await member.setNickname('fart piss')
|
||||||
|
.then(() => interaction.reply(`sucessfully fart pissed on ${member.user.username} <:youngtroll:488559163832795136>`))
|
||||||
|
.catch((error) => {
|
||||||
|
if (process.env.NODE_ENV === 'development') console.error(error);
|
||||||
|
interaction.reply(`Sorry i could not fart piss on ${member.user.username} :(`);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return await interaction.member.setNickname('fart piss')
|
||||||
|
.then(() => interaction.reply('sucessfully fart pissed on you <:youngtroll:488559163832795136>'))
|
||||||
|
.catch((error) => {
|
||||||
|
if (process.env.NODE_ENV === 'development') console.error(error);
|
||||||
|
interaction.reply('Sorry i could not fart piss on you :(');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
|
@ -1,31 +1,31 @@
|
||||||
import { SlashCommandBuilder } from '@discordjs/builders';
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
import { MessageEmbed } from 'discord.js';
|
import { EmbedBuilder } from 'discord.js';
|
||||||
import { exec } from 'node:child_process';
|
import { execFile } from 'node:child_process';
|
||||||
import db from '../../models/index.js';
|
import db from '../../models/index.js';
|
||||||
const donator = db.donator;
|
const donator = db.donator;
|
||||||
|
|
||||||
import dotenv from 'dotenv';
|
const { ownerId, uptimePage } = process.env;
|
||||||
dotenv.config();
|
|
||||||
const { ownerId } = process.env;
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
.setName('about')
|
.setName('about')
|
||||||
.setDescription('About me (The bot)'),
|
.setDescription('About me (The bot)'),
|
||||||
|
category: 'utility',
|
||||||
async execute(interaction) {
|
async execute(interaction) {
|
||||||
const Donator = await donator.findAll({ order: ['id'] });
|
const Donator = await donator.findAll({ order: ['id'] });
|
||||||
const client = interaction.client;
|
const client = interaction.client;
|
||||||
const tina = await client.users.fetch('336492042299637771');
|
const tina = await client.users.fetch('336492042299637771');
|
||||||
const owner = await client.users.fetch('267065637183029248');
|
const creator = await client.users.fetch('267065637183029248');
|
||||||
const maintainer = await client.users.fetch(ownerId);
|
const maintainer = await client.users.fetch(ownerId);
|
||||||
|
|
||||||
let description = `This bot is made using [discord.js](https://github.com/discordjs/discord.js)\nThanks to ${tina.tag} (336492042299637771) for inspiring me for making this bot!\n\nThe people who donated for the bot <3\n`;
|
let description = 'I\'m a fun multipurpose bot made using [discord.js](https://github.com/discordjs/discord.js)'
|
||||||
|
+ '\nFor a better experience use the slash commands!\n\nThe people who donated for the bot <3\n';
|
||||||
|
|
||||||
if (Donator[0]) {
|
if (Donator[0]) {
|
||||||
for (let i = 0; i < Donator.length; i++) {
|
for (let i = 0; i < Donator.length; i++) {
|
||||||
const user = await client.users.fetch(Donator[i].get('userID').toString());
|
const user = await client.users.fetch(Donator[i].get('userID').toString());
|
||||||
if (user !== null) {
|
if (user !== null) {
|
||||||
description += `**${user.tag} (${user.id}) | ${Donator[i].get('comment')}**\n`;
|
description += `**${user.username} (${user.id}) | ${Donator[i].get('comment')}**\n`;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
description += `**A user of discord (${user.id}) | ${Donator[i].get('comment')} (This user no longer share a server with the bot)**\n`;
|
description += `**A user of discord (${user.id}) | ${Donator[i].get('comment')} (This user no longer share a server with the bot)**\n`;
|
||||||
|
@ -36,20 +36,26 @@ export default {
|
||||||
description += 'No one :(\n';
|
description += 'No one :(\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
description += `\nThanks to ${tina.username} (336492042299637771) for inspiring me for making this bot!`;
|
||||||
|
|
||||||
// description += '\nThanks to Jetbrains for providing their IDE!';
|
// description += '\nThanks to Jetbrains for providing their IDE!';
|
||||||
|
|
||||||
exec('git rev-parse --short HEAD', (err, stdout) => {
|
execFile('git', ['rev-parse', '--short', 'HEAD'], (err, stdout) => {
|
||||||
const aboutEmbed = new MessageEmbed()
|
const aboutEmbed = new EmbedBuilder()
|
||||||
.setColor(interaction.member ? interaction.member.displayHexColor : 'NAVY')
|
.setColor(interaction.member ? interaction.member.displayHexColor : 'Navy')
|
||||||
.setAuthor({ name: client.user.tag, iconURL: client.user.displayAvatarURL(), url: 'https://libtar.de' })
|
.setAuthor({ name: client.user.username, iconURL: client.user.displayAvatarURL(), url: 'https://libtar.de' })
|
||||||
.setTitle('About me')
|
.setTitle('About me')
|
||||||
.setDescription(description)
|
.setDescription(description)
|
||||||
.addField('Current commit', stdout)
|
.addFields(
|
||||||
.addField('Current maintainer: ', `${maintainer.tag} (${ownerId})`)
|
{ name: 'Current commit', value: stdout },
|
||||||
.addField('Gitea (Main)', 'https://git.namejeff.xyz/Supositware/Haha-Yes', true)
|
{ name: 'Current maintainer', value: `${maintainer.username} (${ownerId})` },
|
||||||
.addField('Github (Mirror)', 'https://github.com/Supositware/Haha-yes', true)
|
{ name: 'Gitea (Main)', value: 'https://git.namejeff.xyz/Supositware/Haha-Yes', inline: true },
|
||||||
.addField('Privacy Policy', 'https://libtar.de/discordprivacy.txt')
|
{ name: 'Github (Mirror)', value: 'https://github.com/Supositware/Haha-yes', inline: true },
|
||||||
.setFooter({ text: `Original bot made by ${owner.tag} (267065637183029248)` });
|
{ name: 'Privacy Policy', value: 'https://libtar.de/discordprivacy.txt', inline: true },
|
||||||
|
{ name: 'Status page', value: uptimePage.toString(), inline: true },
|
||||||
|
|
||||||
|
)
|
||||||
|
.setFooter({ text: `Original bot made by ${creator.username} (267065637183029248)` });
|
||||||
|
|
||||||
interaction.reply({ embeds: [aboutEmbed] });
|
interaction.reply({ embeds: [aboutEmbed] });
|
||||||
});
|
});
|
||||||
|
|
99
commands/utility/addytp.js
Normal file
99
commands/utility/addytp.js
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
import { SlashCommandBuilder, EmbedBuilder } from 'discord.js';
|
||||||
|
import utils from '../../utils/videos.js';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import os from 'node:os';
|
||||||
|
|
||||||
|
const { ytpChannelId } = process.env;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('addytp')
|
||||||
|
.setDescription('Add a video to the pool of ytps. You can add 5 per day.')
|
||||||
|
.addStringOption(option =>
|
||||||
|
option.setName('url')
|
||||||
|
.setDescription('URL of the video you want to add.')
|
||||||
|
.setRequired(true)),
|
||||||
|
category: 'utility',
|
||||||
|
ratelimit: 5,
|
||||||
|
cooldown: 86400,
|
||||||
|
async execute(interaction, args) {
|
||||||
|
const url = args.url;
|
||||||
|
// This is rather rudementary, a proper way would be using yt-dlp to know if it is a playlist
|
||||||
|
if (url.includes('list=')) {
|
||||||
|
return interaction.reply({ content: '❌ Playlists are not allowed!', ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!await utils.stringIsAValidurl(url)) {
|
||||||
|
console.error(`Not a url!!! ${url}`);
|
||||||
|
return interaction.reply({ content: '❌ This does not look like a valid url!', ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
await interaction.deferReply({ ephemeral: true });
|
||||||
|
|
||||||
|
utils.downloadVideo(url, interaction.id, 'bestvideo[height<=?480]+bestaudio/best')
|
||||||
|
.then(async () => {
|
||||||
|
const file = fs.readdirSync(os.tmpdir()).filter(fn => fn.startsWith(interaction.id));
|
||||||
|
const output = `${os.tmpdir()}/${file}`;
|
||||||
|
|
||||||
|
const fileStat = fs.statSync(output);
|
||||||
|
const fileSize = fileStat.size / 1000000.0;
|
||||||
|
|
||||||
|
if (fileSize > 50) {
|
||||||
|
// await interaction.deleteReply();
|
||||||
|
await interaction.editReply({ content: '❌ Uh oh! The video is too big!', ephemeral: true });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// CopyFile instead of rename in case you have /tmp and the asset folder on different a disk.
|
||||||
|
fs.copyFileSync(output, `./asset/ytp/userVid/${file}`);
|
||||||
|
const mp4 = [];
|
||||||
|
fs.readdirSync('./asset/ytp/userVid/').forEach(f => {
|
||||||
|
if (f.endsWith('mp4')) {
|
||||||
|
mp4.push(f);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// (Hopefully) limit video to 2k
|
||||||
|
if (mp4.length > 2000) {
|
||||||
|
const f = mp4.sort((a, b) => {
|
||||||
|
const time1 = fs.statSync(`./asset/ytp/userVid/${b}`).ctime;
|
||||||
|
const time2 = fs.statSync(`./asset/ytp/userVid/${a}`).ctime;
|
||||||
|
if (time1 < time2) return 1;
|
||||||
|
if (time1 > time2) return -1;
|
||||||
|
return 0;
|
||||||
|
}).slice(0, 1);
|
||||||
|
fs.unlinkSync(`./asset/ytp/userVid/${f[0]}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
interaction.editReply({ content: `Video successfully added to the pool! There is now ${mp4.length} videos`, ephemeral: true });
|
||||||
|
|
||||||
|
const Embed = new EmbedBuilder()
|
||||||
|
.setAuthor({ name: interaction.user.username, iconURL: interaction.user.displayAvatarURL() })
|
||||||
|
.addFields([
|
||||||
|
{ name: 'Channel ID', value: interaction.channel.id.toString(), inline: true },
|
||||||
|
{ name: 'Message ID', value: interaction.id.toString(), inline: true },
|
||||||
|
{ name: 'Author', value: `${interaction.user.username} (${interaction.user.id})`, inline: true },
|
||||||
|
])
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
if (interaction.guild) {
|
||||||
|
Embed.addFields([
|
||||||
|
{ name: 'Guild', value: `${interaction.guild.name} (${interaction.guild.id})`, inline: true },
|
||||||
|
{ name: 'Message link', value: `https://discord.com/channels/${interaction.guild.id}/${interaction.channel.id}/${interaction.id}` },
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Embed.addFields([
|
||||||
|
{ name: 'Message link', value: `https://discord.com/channels/@me/${interaction.channel.id}/${interaction.id}` },
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const channel = interaction.client.channels.resolve(ytpChannelId);
|
||||||
|
// Send as 2 separate message otherwise the url won't get embedded.
|
||||||
|
channel.send({ content: url });
|
||||||
|
return channel.send({ embeds: [Embed] });
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
49
commands/utility/avatar.js
Normal file
49
commands/utility/avatar.js
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import { EmbedBuilder, SlashCommandBuilder } from 'discord.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('avatar')
|
||||||
|
.setDescription('Show user avatar')
|
||||||
|
.addMentionableOption(option =>
|
||||||
|
option.setName('member')
|
||||||
|
.setDescription('Who do you want to fake?')
|
||||||
|
.setRequired(false)),
|
||||||
|
category: 'utility',
|
||||||
|
async execute(interaction, args) {
|
||||||
|
const avatarEmbed = new EmbedBuilder()
|
||||||
|
.setColor(interaction.member ? interaction.member.displayHexColor : 'Navy')
|
||||||
|
.setTitle('Avatar');
|
||||||
|
|
||||||
|
|
||||||
|
if (!args.member) {
|
||||||
|
const format = interaction.user.displayAvatarURL({ dynamic: true }).substr(interaction.user.displayAvatarURL({ dynamic: true }).length - 3);
|
||||||
|
if (format == 'gif') {
|
||||||
|
avatarEmbed.setAuthor({ name: interaction.user.username });
|
||||||
|
avatarEmbed.setDescription(`[gif](${interaction.user.displayAvatarURL({ format: 'gif', size: 2048 })})`);
|
||||||
|
avatarEmbed.setImage(interaction.user.displayAvatarURL({ format: 'gif', size: 2048 }));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
avatarEmbed.setAuthor({ name: interaction.user.username });
|
||||||
|
avatarEmbed.setDescription(`[png](${interaction.user.displayAvatarURL({ format: 'png', size: 2048 })}) | [jpeg](${interaction.user.displayAvatarURL({ format: 'jpg', size: 2048 })}) | [webp](${interaction.user.displayAvatarURL({ format: 'webp', size: 2048 })})`);
|
||||||
|
avatarEmbed.setImage(interaction.user.displayAvatarURL({ format: 'png', size: 2048 }));
|
||||||
|
}
|
||||||
|
return interaction.reply({ embeds: [avatarEmbed] });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await interaction.guild.members.fetch();
|
||||||
|
const format = args.member.displayAvatarURL({ dynamic: true }).substr(args.member.displayAvatarURL({ dynamic: true }).length - 3);
|
||||||
|
if (format == 'gif') {
|
||||||
|
avatarEmbed.setAuthor({ name: args.member.user.username });
|
||||||
|
avatarEmbed.setDescription(`[gif](${args.member.displayAvatarURL({ format: 'gif', size: 2048 })})`);
|
||||||
|
avatarEmbed.setImage(args.member.displayAvatarURL({ format: 'gif', size: 2048 }));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
avatarEmbed.setAuthor({ name: args.member.user.username });
|
||||||
|
avatarEmbed.setDescription(`[png](${args.member.displayAvatarURL({ format: 'png', size: 2048 })}) | [jpeg](${args.member.displayAvatarURL({ format: 'jpg', size: 2048 })}) | [webp](${args.member.displayAvatarURL({ format: 'webp', size: 2048 })})`);
|
||||||
|
avatarEmbed.setImage(args.member.displayAvatarURL({ format: 'png', size: 2048 }));
|
||||||
|
}
|
||||||
|
return interaction.reply({ embeds: [avatarEmbed] });
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
};
|
|
@ -1,19 +1,20 @@
|
||||||
import { SlashCommandBuilder } from '@discordjs/builders';
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
import { MessageEmbed } from 'discord.js';
|
import { EmbedBuilder } from 'discord.js';
|
||||||
import donations from '../../json/donations.json' assert {type: 'json'};
|
import donations from '../../json/donations.json' with {type: 'json'};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
.setName('donate')
|
.setName('donate')
|
||||||
.setDescription('Show donation link for the bot.'),
|
.setDescription('Show donation link for the bot.'),
|
||||||
|
category: 'utility',
|
||||||
async execute(interaction) {
|
async execute(interaction) {
|
||||||
let desc = 'If you decide to donate, please do /feedback to let the owner know about it so he can put you in the about and donator command.';
|
let desc = 'If you decide to donate, please do /feedback to let the owner know about it so he can put you in the about and donator command.';
|
||||||
donations.forEach(m => {
|
donations.forEach(m => {
|
||||||
desc += `\n${m}`;
|
desc += `\n${m}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
const Embed = new MessageEmbed()
|
const Embed = new EmbedBuilder()
|
||||||
.setColor(interaction.member ? interaction.member.displayHexColor : 'NAVY')
|
.setColor(interaction.member ? interaction.member.displayHexColor : 'Navy')
|
||||||
.setTitle('Donation link')
|
.setTitle('Donation link')
|
||||||
.setDescription(desc);
|
.setDescription(desc);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { SlashCommandBuilder } from '@discordjs/builders';
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
import db from '../../models/index.js';
|
import db from '../../models/index.js';
|
||||||
const donator = db.donator;
|
const donator = db.donator;
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ export default {
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
.setName('donator')
|
.setName('donator')
|
||||||
.setDescription('All the people who donated for this bot <3'),
|
.setDescription('All the people who donated for this bot <3'),
|
||||||
|
category: 'utility',
|
||||||
async execute(interaction) {
|
async execute(interaction) {
|
||||||
await interaction.deferReply();
|
await interaction.deferReply();
|
||||||
const client = interaction.client;
|
const client = interaction.client;
|
||||||
|
@ -16,7 +17,7 @@ export default {
|
||||||
if (Donator[0]) {
|
if (Donator[0]) {
|
||||||
for (let i = 0; i < Donator.length; i++) {
|
for (let i = 0; i < Donator.length; i++) {
|
||||||
const user = await client.users.fetch(Donator[i].get('userID').toString());
|
const user = await client.users.fetch(Donator[i].get('userID').toString());
|
||||||
if (user !== null) {donatorMessage += `**${user.tag} (${user.id}) | ${Donator[i].get('comment')}**\n`;}
|
if (user !== null) {donatorMessage += `**${user.username} (${user.id}) | ${Donator[i].get('comment')}**\n`;}
|
||||||
else {donatorMessage += `**A user of discord (${user.id}) | ${Donator[i].get('comment')} (This user no longer share a server with the bot)**\n`;}
|
else {donatorMessage += `**A user of discord (${user.id}) | ${Donator[i].get('comment')} (This user no longer share a server with the bot)**\n`;}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
import { SlashCommandBuilder } from '@discordjs/builders';
|
import { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, StringSelectMenuBuilder } from 'discord.js';
|
||||||
import { MessageEmbed, MessageActionRow, MessageSelectMenu } from 'discord.js';
|
import { execFile } from 'node:child_process';
|
||||||
import { exec } from 'node:child_process';
|
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
import utils from '../../utils/videos.js';
|
import utils from '../../utils/videos.js';
|
||||||
|
|
||||||
|
let client;
|
||||||
|
let maxFileSize;
|
||||||
|
|
||||||
|
let { ytdlpMaxResolution } = process.env;
|
||||||
|
const { proxy } = process.env;
|
||||||
|
// Convert to number as process.env is always a string
|
||||||
|
ytdlpMaxResolution = Number(ytdlpMaxResolution);
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
.setName('download')
|
.setName('download')
|
||||||
|
@ -19,21 +26,47 @@ export default {
|
||||||
.setRequired(false))
|
.setRequired(false))
|
||||||
.addBooleanOption(option =>
|
.addBooleanOption(option =>
|
||||||
option.setName('compress')
|
option.setName('compress')
|
||||||
.setDescription('Compress the video?')
|
.setDescription('Compress the video.')
|
||||||
|
.setRequired(false))
|
||||||
|
.addBooleanOption(option =>
|
||||||
|
option.setName('autocrop')
|
||||||
|
.setDescription('Autocrop borders on videos. Ignored when using compress option.')
|
||||||
|
.setRequired(false))
|
||||||
|
.addBooleanOption(option =>
|
||||||
|
option.setName('description')
|
||||||
|
.setDescription('Include the video description.')
|
||||||
.setRequired(false)),
|
.setRequired(false)),
|
||||||
|
category: 'utility',
|
||||||
|
alias: ['dl'],
|
||||||
|
integration_types: [0, 1],
|
||||||
|
|
||||||
|
async execute(interaction, args, c) {
|
||||||
|
client = c;
|
||||||
|
const url = args.url;
|
||||||
|
const format = args.format;
|
||||||
|
maxFileSize = await utils.getMaxFileSize(interaction.guild);
|
||||||
|
interaction.doCompress = args.compress;
|
||||||
|
interaction.doAutocrop = args.autocrop;
|
||||||
|
|
||||||
async execute(interaction) {
|
|
||||||
await interaction.deferReply({ ephemeral: false });
|
await interaction.deferReply({ ephemeral: false });
|
||||||
const url = interaction.options.getString('url');
|
|
||||||
|
if (interaction.isMessage) {
|
||||||
|
interaction.delete();
|
||||||
|
}
|
||||||
|
|
||||||
if (!await utils.stringIsAValidurl(url)) {
|
if (!await utils.stringIsAValidurl(url)) {
|
||||||
console.error(`Not a url!!! ${url}`);
|
console.error(`Not a url!!! ${url}`);
|
||||||
return interaction.editReply({ content: '❌ This does not look like a valid url!', ephemeral: true });
|
return interaction.editReply({ content: '❌ This does not look like a valid url!', ephemeral: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (interaction.options.getBoolean('format')) {
|
if (format) {
|
||||||
let qualitys = await new Promise((resolve, reject) => {
|
let qualitys = await new Promise((resolve, reject) => {
|
||||||
exec(`./bin/yt-dlp "${url}" --print "%()j"`, (err, stdout, stderr) => {
|
const options = [url, '--print', '%()j'];
|
||||||
|
if (proxy) {
|
||||||
|
options.push('--proxy');
|
||||||
|
options.push(proxy);
|
||||||
|
};
|
||||||
|
execFile('./bin/yt-dlp', options, (err, stdout, stderr) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(stderr);
|
reject(stderr);
|
||||||
}
|
}
|
||||||
|
@ -43,11 +76,12 @@ export default {
|
||||||
resolve(stdout);
|
resolve(stdout);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
qualitys = JSON.parse(qualitys);
|
|
||||||
|
|
||||||
|
qualitys = JSON.parse(qualitys);
|
||||||
const options = [];
|
const options = [];
|
||||||
|
|
||||||
qualitys.formats.forEach(f => {
|
qualitys.formats.forEach(f => {
|
||||||
|
if (f.format.includes('storyboard')) return;
|
||||||
options.push({
|
options.push({
|
||||||
label: f.resolution ? f.resolution : 'Unknown format',
|
label: f.resolution ? f.resolution : 'Unknown format',
|
||||||
description: `${f.format} V: ${f.vcodec} A: ${f.acodec}`,
|
description: `${f.format} V: ${f.vcodec} A: ${f.acodec}`,
|
||||||
|
@ -71,10 +105,10 @@ export default {
|
||||||
options.reverse();
|
options.reverse();
|
||||||
}
|
}
|
||||||
|
|
||||||
const row = new MessageActionRow()
|
const row = new ActionRowBuilder()
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new MessageSelectMenu()
|
new StringSelectMenuBuilder()
|
||||||
.setCustomId('downloadQuality')
|
.setCustomId(`downloadQuality${interaction.user.id}${interaction.id}`)
|
||||||
.setPlaceholder('Nothing selected')
|
.setPlaceholder('Nothing selected')
|
||||||
.setMinValues(1)
|
.setMinValues(1)
|
||||||
.setMaxValues(2)
|
.setMaxValues(2)
|
||||||
|
@ -84,27 +118,40 @@ export default {
|
||||||
await interaction.deleteReply();
|
await interaction.deleteReply();
|
||||||
await interaction.followUp({ content: 'Which quality do you want?', ephemeral: true, components: [row] });
|
await interaction.followUp({ content: 'Which quality do you want?', ephemeral: true, components: [row] });
|
||||||
|
|
||||||
interaction.client.once('interactionCreate', async (interactionMenu) => {
|
client.on('interactionCreate', async (interactionMenu) => {
|
||||||
|
if (interaction.user !== interactionMenu.user) return;
|
||||||
if (!interactionMenu.isSelectMenu()) return;
|
if (!interactionMenu.isSelectMenu()) return;
|
||||||
if (interactionMenu.customId === 'downloadQuality') {
|
if (interactionMenu.customId === `downloadQuality${interaction.user.id}${interaction.id}`) {
|
||||||
await interactionMenu.deferReply({ ephemeral: false });
|
await interactionMenu.deferReply({ ephemeral: false });
|
||||||
download(url, interactionMenu, interaction);
|
|
||||||
|
await checkSize(url, interactionMenu.values[0], args, interaction);
|
||||||
|
return download(url, interactionMenu, interaction, undefined, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
download(url, interaction);
|
const newFormat = await checkSize(url, undefined, args, interaction);
|
||||||
|
return download(url, interaction, interaction, newFormat, args.description);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
async function download(url, interaction, originalInteraction) {
|
async function download(url, interaction, originalInteraction, format = undefined, description = false) {
|
||||||
let format = 'bestvideo*+bestaudio/best';
|
let embedColour = 'Navy';
|
||||||
const Embed = new MessageEmbed()
|
if (interaction.member) {
|
||||||
.setColor(interaction.member ? interaction.member.displayHexColor : 'NAVY')
|
if (interaction.member.displayHexColor) {
|
||||||
.setAuthor({ name: `Downloaded by ${interaction.user.tag}`, iconURL: interaction.user.displayAvatarURL(), url: url })
|
embedColour = interaction.member.displayHexColor;
|
||||||
.setFooter({ text: `You can get the original video by clicking on the "Downloaded by ${interaction.user.tag}" message!` });
|
}
|
||||||
|
}
|
||||||
|
const Embed = new EmbedBuilder()
|
||||||
|
.setColor(embedColour)
|
||||||
|
.setAuthor({ name: `Downloaded by ${interaction.user.username}`, iconURL: interaction.user.displayAvatarURL(), url: url })
|
||||||
|
.setFooter({ text: `You can get the original video by clicking on the "Downloaded by ${interaction.user.username}" message!` });
|
||||||
|
|
||||||
if (interaction.customId === 'downloadQuality') {
|
if (description) {
|
||||||
|
Embed.setDescription(await getVideoDescription(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interaction.customId === `downloadQuality${interaction.user.id}${originalInteraction.id}` && !format) {
|
||||||
format = interaction.values[0];
|
format = interaction.values[0];
|
||||||
if (interaction.values[1]) format += '+' + interaction.values[1];
|
if (interaction.values[1]) format += '+' + interaction.values[1];
|
||||||
}
|
}
|
||||||
|
@ -112,13 +159,11 @@ async function download(url, interaction, originalInteraction) {
|
||||||
utils.downloadVideo(url, interaction.id, format)
|
utils.downloadVideo(url, interaction.id, format)
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
const file = fs.readdirSync(os.tmpdir()).filter(fn => fn.startsWith(interaction.id));
|
const file = fs.readdirSync(os.tmpdir()).filter(fn => fn.startsWith(interaction.id));
|
||||||
const output = `${os.tmpdir()}/${file}`;
|
let output = `${os.tmpdir()}/${file}`;
|
||||||
|
|
||||||
const fileStat = fs.statSync(output);
|
|
||||||
const fileSize = fileStat.size / 1000000.0;
|
|
||||||
const compressInteraction = originalInteraction ? originalInteraction : interaction;
|
const compressInteraction = originalInteraction ? originalInteraction : interaction;
|
||||||
if (compressInteraction.options.getBoolean('compress')) {
|
if (compressInteraction.doCompress) {
|
||||||
const presets = [ 'Discord Tiny 5 Minutes 240p30', 'Discord Small 2 Minutes 360p30', 'Discord Nitro Small 10-20 Minutes 480p30', 'Discord Nitro Medium 5-10 Minutes 720p30', 'Discord Nitro Large 3-6 Minutes 1080p30' ];
|
const presets = [ 'Social 25 MB 5 Minutes 360p60', 'Social 25 MB 2 Minutes 540p60', 'Social 25 MB 1 Minute 720p60', 'Social 25 MB 30 Seconds 1080p60' ];
|
||||||
const options = [];
|
const options = [];
|
||||||
|
|
||||||
presets.forEach(p => {
|
presets.forEach(p => {
|
||||||
|
@ -128,41 +173,88 @@ async function download(url, interaction, originalInteraction) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const row = new MessageActionRow()
|
const row = new ActionRowBuilder()
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new MessageSelectMenu()
|
new StringSelectMenuBuilder()
|
||||||
.setCustomId('preset')
|
.setCustomId(`preset${interaction.user.id}${interaction.id}`)
|
||||||
.setPlaceholder('Nothing selected')
|
.setPlaceholder('Nothing selected')
|
||||||
.addOptions(options),
|
.addOptions(options),
|
||||||
);
|
);
|
||||||
|
|
||||||
await interaction.deleteReply();
|
await interaction.deleteReply();
|
||||||
await interaction.followUp({ content: 'Which compression preset do you want?', ephemeral: true, components: [row] });
|
await interaction.followUp({ content: 'Which compression preset do you want?', ephemeral: true, components: [row] });
|
||||||
interaction.client.once('interactionCreate', async (interactionMenu) => {
|
client.on('interactionCreate', async (interactionMenu) => {
|
||||||
|
if (interaction.user !== interactionMenu.user) return;
|
||||||
if (!interactionMenu.isSelectMenu()) return;
|
if (!interactionMenu.isSelectMenu()) return;
|
||||||
if (interactionMenu.customId === 'preset') {
|
if (interactionMenu.customId === `preset${interaction.user.id}${interaction.id}`) {
|
||||||
await interactionMenu.deferReply({ ephemeral: false });
|
await interactionMenu.deferReply({ ephemeral: false });
|
||||||
compress(file, interactionMenu, Embed);
|
compress(file, interactionMenu, Embed);
|
||||||
|
if (interaction.isMessage) {
|
||||||
|
interaction.deleteReply();
|
||||||
|
interaction.cleanUp();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the video format is not one compatible with Discord, reencode it unless autocrop is choosen in which case it gets reencoded anyway.
|
||||||
|
if (!interaction.doAutocrop) {
|
||||||
|
const bannedFormats = ['av1'];
|
||||||
|
const codec = await utils.getVideoCodec(output);
|
||||||
|
|
||||||
|
if (bannedFormats.includes(codec)) {
|
||||||
|
const oldOutput = output;
|
||||||
|
output = `${os.tmpdir()}/264${file}`;
|
||||||
|
await utils.ffmpeg(['-i', oldOutput, '-vcodec', 'libx264', '-acodec', 'aac', output]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (interaction.doAutocrop && !compressInteraction.doCompress) {
|
||||||
|
const oldOutput = output;
|
||||||
|
output = `${os.tmpdir()}/autocrop${file}`;
|
||||||
|
await utils.autoCrop(oldOutput, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileStat = fs.statSync(output);
|
||||||
|
const fileSize = fileStat.size / 1000000.0;
|
||||||
|
|
||||||
|
Embed.setAuthor({ name: `${Embed.data.author.name} (${fileSize.toFixed(2)} MB)`, iconURL: Embed.data.author.icon_url, url: Embed.data.author.url });
|
||||||
|
|
||||||
|
let message = null;
|
||||||
|
if (interaction.isMessage && interaction.reference !== null) {
|
||||||
|
const channel = client.channels.resolve(interaction.reference.channelId);
|
||||||
|
message = await channel.messages.fetch(interaction.reference.messageId);
|
||||||
|
}
|
||||||
|
|
||||||
if (fileSize > 100) {
|
if (fileSize > 100) {
|
||||||
await interaction.deleteReply();
|
await interaction.deleteReply();
|
||||||
await interaction.followUp('Uh oh! The video you tried to download is too big!', { ephemeral: true });
|
await interaction.followUp('Uh oh! The video you tried to download is too big!', { ephemeral: true });
|
||||||
}
|
}
|
||||||
else if (fileSize > 8) {
|
else if (fileSize > maxFileSize) {
|
||||||
const fileurl = await utils.upload(output)
|
const fileurl = await utils.upload(output)
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
await interaction.editReply({ content: 'File was bigger than 8 mb. It has been uploaded to an external site.', embeds: [Embed], ephemeral: false });
|
|
||||||
await interaction.followUp({ content: fileurl, ephemeral: false });
|
await interaction.editReply({ content: `File was bigger than ${maxFileSize} mb. It has been uploaded to an external site.`, embeds: [Embed], ephemeral: false });
|
||||||
|
if (interaction.isMessage && message) {
|
||||||
|
await message.reply({ content: fileurl });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await interaction.followUp({ content: fileurl, ephemeral: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (interaction.isMessage && message) {
|
||||||
|
await message.reply({ embeds: [Embed], files: [output] });
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
await interaction.editReply({ embeds: [Embed], files: [output], ephemeral: false });
|
await interaction.editReply({ embeds: [Embed], files: [output], ephemeral: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (interaction.isMessage) {
|
||||||
|
interaction.deleteReply();
|
||||||
|
interaction.cleanUp();
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(async err => {
|
.catch(async err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
@ -174,16 +266,69 @@ async function download(url, interaction, originalInteraction) {
|
||||||
|
|
||||||
async function compress(input, interaction, embed) {
|
async function compress(input, interaction, embed) {
|
||||||
const output = `compressed${input}.mp4`;
|
const output = `compressed${input}.mp4`;
|
||||||
|
// Delete the file as it apparently don't overwrite?
|
||||||
|
if (fs.existsSync(output)) {
|
||||||
|
fs.rmSync(output);
|
||||||
|
}
|
||||||
|
|
||||||
utils.compressVideo(`${os.tmpdir()}/${input}`, output, interaction.values[0])
|
utils.compressVideo(`${os.tmpdir()}/${input}`, output, interaction.values[0])
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
const fileStat = fs.statSync(`${os.tmpdir()}/${output}`);
|
const fileStat = fs.statSync(`${os.tmpdir()}/${output}`);
|
||||||
const fileSize = fileStat.size / 1000000.0;
|
const fileSize = fileStat.size / 1000000.0;
|
||||||
|
|
||||||
if (fileSize > 8) {
|
embed.setAuthor({ name: `${embed.data.author.name} (${fileSize.toFixed(2)} MB)`, iconURL: embed.data.author.icon_url, url: embed.data.author.url });
|
||||||
await interaction.editReply({ content: 'File was bigger than 8 mb. but due to the compression it is not being uploaded externally.', ephemeral: true });
|
|
||||||
|
if (fileSize > maxFileSize) {
|
||||||
|
await interaction.editReply({ content: `File was bigger than ${maxFileSize} mb. It has been uploaded to an external site.`, ephemeral: false });
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
await interaction.editReply({ embeds: [embed], files: [`${os.tmpdir()}/${output}`], ephemeral: false });
|
await interaction.editReply({ embeds: [embed], files: [`${os.tmpdir()}/${output}`], ephemeral: false });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkSize(url, format, args, interaction, tries = 0) {
|
||||||
|
const resolutions = [144, 240, 360, 480, 720, 1080, 1440, 2160];
|
||||||
|
|
||||||
|
while (tries < 4) {
|
||||||
|
format = `bestvideo[height<=?${resolutions[resolutions.indexOf(ytdlpMaxResolution) - tries]}]+bestaudio/best`;
|
||||||
|
const aproxFileSize = await utils.getVideoSize(url, format);
|
||||||
|
if (isNaN(aproxFileSize)) return format;
|
||||||
|
|
||||||
|
if (format || tries >= 4) {
|
||||||
|
if (aproxFileSize > 100 && !args.compress && tries > 4) {
|
||||||
|
return await interaction.followUp(`Uh oh! The video you tried to download is larger than 100 mb (is ${aproxFileSize} mb)! Try again with a lower resolution format.`);
|
||||||
|
}
|
||||||
|
else if (aproxFileSize > 500 && tries > 4) {
|
||||||
|
return await interaction.followUp(`Uh oh! The video you tried to download is larger than 500 mb (is ${aproxFileSize} mb)! Try again with a lower resolution format.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aproxFileSize < 100) {
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tries < 4 && aproxFileSize > 100) {
|
||||||
|
tries++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getVideoDescription(urlArg) {
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
const options = [urlArg, '--no-warnings', '-O', '%(description)s'];
|
||||||
|
if (proxy) {
|
||||||
|
options.push('--proxy');
|
||||||
|
options.push(proxy);
|
||||||
|
};
|
||||||
|
execFile('./bin/yt-dlp', options, (err, stdout, stderr) => {
|
||||||
|
if (err) {
|
||||||
|
reject(stderr);
|
||||||
|
}
|
||||||
|
if (stderr) {
|
||||||
|
console.error(stderr);
|
||||||
|
}
|
||||||
|
resolve(stdout.slice(0, 240));
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { SlashCommandBuilder } from '@discordjs/builders';
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
import { MessageEmbed } from 'discord.js';
|
import { EmbedBuilder } from 'discord.js';
|
||||||
|
|
||||||
const { feedbackChannelId } = process.env;
|
const { feedbackChannelId } = process.env;
|
||||||
|
|
||||||
|
@ -10,14 +10,19 @@ export default {
|
||||||
.addStringOption(option =>
|
.addStringOption(option =>
|
||||||
option.setName('feedback')
|
option.setName('feedback')
|
||||||
.setDescription('The message you want to send me.')
|
.setDescription('The message you want to send me.')
|
||||||
.setRequired(true)),
|
.setRequired(true))
|
||||||
async execute(interaction) {
|
.addAttachmentOption(option =>
|
||||||
const Embed = new MessageEmbed()
|
option.setName('image')
|
||||||
.setAuthor({ name: `${interaction.user.tag} (${interaction.user.id})`, iconURL: interaction.user.avatarURL() })
|
.setDescription('Optional attachment.')
|
||||||
|
.setRequired(false)),
|
||||||
|
category: 'utility',
|
||||||
|
async execute(interaction, args) {
|
||||||
|
const Embed = new EmbedBuilder()
|
||||||
|
.setAuthor({ name: `${interaction.user.username} (${interaction.user.id})`, iconURL: interaction.user.avatarURL() })
|
||||||
.setTimestamp();
|
.setTimestamp();
|
||||||
|
|
||||||
if (interaction.guild) Embed.addField('Guild', `${interaction.guild.name} (${interaction.guild.id})`, true);
|
if (interaction.guild) Embed.addFields({ name: 'Guild', value: `${interaction.guild.name} (${interaction.guild.id})`, inline: true });
|
||||||
Embed.addField('Feedback', interaction.options.getString('feedback'));
|
Embed.addFields({ name: 'Feedback', value: args.feedback, inline: true });
|
||||||
|
|
||||||
// Don't let new account use this command to prevent spam
|
// Don't let new account use this command to prevent spam
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
|
@ -26,7 +31,12 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
const channel = interaction.client.channels.resolve(feedbackChannelId);
|
const channel = interaction.client.channels.resolve(feedbackChannelId);
|
||||||
channel.send({ embeds: [Embed] });
|
if (args.image) {
|
||||||
|
channel.send({ embeds: [Embed], files: [args.image] });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
channel.send({ embeds: [Embed] });
|
||||||
|
}
|
||||||
await interaction.reply({ content: 'Your feedback has been sent! Don\'t forget to have dm open if you want to get an answer from the dev!', ephemeral: true });
|
await interaction.reply({ content: 'Your feedback has been sent! Don\'t forget to have dm open if you want to get an answer from the dev!', ephemeral: true });
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
164
commands/utility/help.js
Normal file
164
commands/utility/help.js
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
import { SlashCommandBuilder, EmbedBuilder, AttachmentBuilder, PermissionsBitField } from 'discord.js';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import ratelimiter from '../../utils/ratelimiter.js';
|
||||||
|
|
||||||
|
const { ownerId, prefix } = process.env;
|
||||||
|
const prefixs = prefix.split(',');
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('help')
|
||||||
|
.setDescription('Displays a list of commands or information about a command.')
|
||||||
|
.addStringOption(option =>
|
||||||
|
option.setName('command')
|
||||||
|
.setDescription('The command you want more details about.')),
|
||||||
|
category: 'utility',
|
||||||
|
async execute(interaction, args, client) {
|
||||||
|
if (args.command) {
|
||||||
|
const command = client.commands.get(args.command);
|
||||||
|
if (!command) return interaction.reply(`Did not found any command named \`\`${args.command}\`\`. Please make sure it is a valid command and not an alias.`);
|
||||||
|
const description = Object.assign({
|
||||||
|
content: 'No description available.',
|
||||||
|
usage: '',
|
||||||
|
examples: [],
|
||||||
|
fields: [],
|
||||||
|
}, command.data);
|
||||||
|
|
||||||
|
const usage = command.data.options.map(cmd => {
|
||||||
|
let type = 'String';
|
||||||
|
const constructorName = cmd.constructor.name.toLowerCase();
|
||||||
|
if (constructorName.includes('boolean')) {
|
||||||
|
if (interaction.isMessage) {
|
||||||
|
type = `--${cmd.name}`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
type = 'True/False';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (constructorName.includes('mentionable')) {
|
||||||
|
type = 'User';
|
||||||
|
}
|
||||||
|
else if (constructorName.includes('attachment')) {
|
||||||
|
type = 'Attachment';
|
||||||
|
}
|
||||||
|
|
||||||
|
return `[${cmd.name}: ${type}]`;
|
||||||
|
});
|
||||||
|
|
||||||
|
let p = '/';
|
||||||
|
if (interaction.isMessage) {
|
||||||
|
p = prefixs[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setColor(interaction.member ? interaction.member.displayHexColor : 'Navy')
|
||||||
|
.setTitle(`\`${p}${command.data.name} ${usage.join(' ')}\``)
|
||||||
|
.addFields(
|
||||||
|
{ name: 'Description', value: description.description },
|
||||||
|
)
|
||||||
|
.setFooter({ text: `All the available prefix: ${prefixs.join(' | ')}` });
|
||||||
|
|
||||||
|
for (const field of description.fields) embed.addFields({ name: field.name, value: field.value });
|
||||||
|
|
||||||
|
if (description.examples.length) {
|
||||||
|
const text = `${prefixs[0]}${command.alias[0]}`;
|
||||||
|
embed.addFields({ name: 'Examples', value: `\`${text} ${description.examples.join(`\`\n\`${text} `)}\``, inline: true });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const example = command.data.options.map(cmd => {
|
||||||
|
let string = '"lorem ipsum"';
|
||||||
|
const constructorName = cmd.constructor.name.toLowerCase();
|
||||||
|
if (constructorName.includes('boolean')) {
|
||||||
|
if (interaction.isMessage) {
|
||||||
|
string = `--${cmd.name}`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
string = 'True/False';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (constructorName.includes('mentionable')) {
|
||||||
|
string = `@${interaction.user.username}`;
|
||||||
|
}
|
||||||
|
else if (constructorName.includes('attachment')) {
|
||||||
|
string = 'Attachment';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!interaction.isMessage) {
|
||||||
|
string = `\`\`${cmd.name}:${string}\`\``;
|
||||||
|
}
|
||||||
|
return string;
|
||||||
|
});
|
||||||
|
|
||||||
|
embed.addFields({ name: 'Example', value: `${p}${command.data.name} ${example.join(' ')}`, inline: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.alias) {
|
||||||
|
if (command.alias.length >= 1) {
|
||||||
|
embed.addFields({ name: 'Aliases', value: `\`${command.alias.join('` `')}\``, inline: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (command.userPermissions) {
|
||||||
|
const perm = [];
|
||||||
|
command.userPermissions.forEach(permission => {
|
||||||
|
perm.push(new PermissionsBitField(permission).toArray());
|
||||||
|
});
|
||||||
|
embed.addFields({ name: 'User permission', value: `\`${perm.join('` `')}\``, inline: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.clientPermissions) {
|
||||||
|
const perm = [];
|
||||||
|
command.clientPermissions.forEach(permission => {
|
||||||
|
perm.push(new PermissionsBitField(permission).toArray());
|
||||||
|
});
|
||||||
|
embed.addFields({ name: 'Bot permission', value: `\`${perm.join('` `')}\``, inline: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.parallelLimit) {
|
||||||
|
const paralellimit = ratelimiter.checkParallel(interaction.user, command.data.name, command);
|
||||||
|
|
||||||
|
embed.addFields({ name: 'Current number of executions', value: `\`${paralellimit.current}\``, inline: false });
|
||||||
|
embed.addFields({ name: 'Maximum number of executions', value: `\`${command.parallelLimit}\``, inline: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs.existsSync(`./asset/img/command/${command.category}/${command.data.name}.png`)) {
|
||||||
|
const file = new AttachmentBuilder(`./asset/img/command/${command.category}/${command.data.name}.png`);
|
||||||
|
embed.setImage(`attachment://${command.data.name}.png`);
|
||||||
|
return interaction.reply({ embeds: [embed], files: [file] });
|
||||||
|
}
|
||||||
|
return interaction.reply({ embeds: [embed] });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setColor(interaction.member ? interaction.member.displayHexColor : 'Navy')
|
||||||
|
.addFields({ name: 'Command List', value: `This is a list of commands.\nTo view details for a command, do \`${prefixs[0]}help <command>\`.` })
|
||||||
|
.setFooter({ text: `All the available prefix: ${prefixs.join('| ')}` });
|
||||||
|
|
||||||
|
const object = { };
|
||||||
|
for (const command of client.commands.values()) {
|
||||||
|
if (command.category === 'secret') continue;
|
||||||
|
if (interaction.user.id !== ownerId && command.category === 'owner') continue;
|
||||||
|
if (object[command.category]) {
|
||||||
|
object[command.category].push(command.data.name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
object[command.category] = [ command.data.name ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const category in object) {
|
||||||
|
const title = {
|
||||||
|
fun: '🎉\u2000Fun',
|
||||||
|
utility: '🔩\u2000Utility',
|
||||||
|
admin: '⚡\u2000Admin',
|
||||||
|
owner: '🛠️\u2000Owner',
|
||||||
|
voice: '🗣️\u2000Voice',
|
||||||
|
AI: '🦾\u2000AI',
|
||||||
|
}[category];
|
||||||
|
|
||||||
|
embed.addFields({ name: title, value: `\`${object[category].join('` `')}\`` });
|
||||||
|
}
|
||||||
|
return interaction.reply({ embeds: [embed] });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
27
commands/utility/invite.js
Normal file
27
commands/utility/invite.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('invite')
|
||||||
|
.setDescription('Generate invite link for the bot or another')
|
||||||
|
.addMentionableOption(option =>
|
||||||
|
option.setName('bot')
|
||||||
|
.setDescription('The bot you want to make an invite link for.')
|
||||||
|
.setRequired(false)),
|
||||||
|
category: 'utility',
|
||||||
|
integration_types: [0, 1],
|
||||||
|
async execute(interaction, args, client) {
|
||||||
|
if (args.bot) {
|
||||||
|
if (args.bot.user.bot) {
|
||||||
|
return interaction.reply(`You can add the bot you mentioned with this link: https://discordapp.com/oauth2/authorize?client_id=${args.bot.id}&permissions=2684406848&scope=bot%20applications.commands\n\`Note: The invite will not work if the bot is not public\``);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return interaction.reply('I\'m sorry but the user you mentioned is not a bot!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return interaction.reply(`
|
||||||
|
You can add me for your server from here: https://discord.com/oauth2/authorize?client_id=${client.user.id}&permissions=2684406848&scope=bot%20applications.commands` +
|
||||||
|
`\nIf you want to use my commands no matter the server you can install me as a user applications from here: https://discord.com/oauth2/authorize?client_id=${client.user.id}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
68
commands/utility/optout.js
Normal file
68
commands/utility/optout.js
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import { SlashCommandBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js';
|
||||||
|
import db from '../../models/index.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('optout')
|
||||||
|
.setDescription('Opt out of the non commands features and arguments logging (for debugging purposes)'),
|
||||||
|
category: 'utility',
|
||||||
|
integration_types: [0, 1],
|
||||||
|
|
||||||
|
async execute(interaction, args, client) {
|
||||||
|
const isOptOut = await db.optout.findOne({ where: { userID: interaction.user.id } });
|
||||||
|
|
||||||
|
if (!isOptOut) {
|
||||||
|
const body = { userID: interaction.user.id };
|
||||||
|
await db.optout.create(body);
|
||||||
|
await interaction.reply({ content: 'You have successfully been opt out.', ephemeral: true });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const row = new ActionRowBuilder()
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`yes${interaction.user.id}${interaction.id}`)
|
||||||
|
.setLabel('Yes')
|
||||||
|
.setStyle(ButtonStyle.Primary),
|
||||||
|
)
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(`no${interaction.user.id}${interaction.id}`)
|
||||||
|
.setLabel('No')
|
||||||
|
.setStyle(ButtonStyle.Danger),
|
||||||
|
);
|
||||||
|
|
||||||
|
await interaction.reply({ content: 'You are already opt out, do you wish to opt in?', components: [row], ephemeral: true });
|
||||||
|
|
||||||
|
listenButton(client, interaction, interaction.user);
|
||||||
|
}
|
||||||
|
|
||||||
|
return interaction.followUp({
|
||||||
|
content:
|
||||||
|
'As a reminder here what opting out does:\n'
|
||||||
|
+ '- Your user ID will no longer be used for debug logging.\n'
|
||||||
|
+ '- servers will no longer be shown in added/kicked stats.\n'
|
||||||
|
+ '- Your messages won\'t be quoted.\n'
|
||||||
|
+ '- Won\'t show the arguments from commands.',
|
||||||
|
ephemeral: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
async function listenButton(client, interaction, user = interaction.user, originalId = interaction.id) {
|
||||||
|
client.once('interactionCreate', async (interactionMenu) => {
|
||||||
|
if (user !== interactionMenu.user) return listenButton(client, interaction, user, originalId);
|
||||||
|
if (!interactionMenu.isButton()) return;
|
||||||
|
|
||||||
|
await interactionMenu.update({ components: [] });
|
||||||
|
|
||||||
|
if (interactionMenu.customId === `yes${interaction.user.id}${originalId}`) {
|
||||||
|
db.optout.destroy({ where: { userID: interaction.user.id } });
|
||||||
|
return interaction.editReply({ content: 'You have successfully been opt in', ephemeral: true });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return interaction.editReply({ content: 'Nothing has been changed.', ephemeral: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,10 +1,22 @@
|
||||||
import { SlashCommandBuilder } from '@discordjs/builders';
|
import { SlashCommandBuilder, ButtonBuilder, ActionRowBuilder, ButtonStyle } from 'discord.js';
|
||||||
|
const { uptimePage } = process.env;
|
||||||
export default {
|
export default {
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
.setName('ping')
|
.setName('ping')
|
||||||
.setDescription('Replies with Pong!'),
|
.setDescription('Replies with Pong!'),
|
||||||
|
category: 'utility',
|
||||||
|
integration_types: [0, 1],
|
||||||
|
|
||||||
async execute(interaction) {
|
async execute(interaction) {
|
||||||
await interaction.reply(`Pong! \`${Math.round(interaction.client.ws.ping)} ms\``);
|
const row = new ActionRowBuilder()
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setLabel('Status page')
|
||||||
|
.setURL(uptimePage)
|
||||||
|
.setStyle(ButtonStyle.Link),
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
await interaction.reply({ content: `Pong! \`${Math.round(interaction.client.ws.ping)} ms\``, components: [row] });
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
33
commands/utility/serverinfo.js
Normal file
33
commands/utility/serverinfo.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import { SlashCommandBuilder, EmbedBuilder } from 'discord.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('serverinfo')
|
||||||
|
.setDescription('Show info about the server'),
|
||||||
|
category: 'utility',
|
||||||
|
alias: ['server'],
|
||||||
|
async execute(interaction, args, client) {
|
||||||
|
await interaction.guild.members.fetch();
|
||||||
|
const botCount = interaction.guild.members.cache.filter(member => member.user.bot).size;
|
||||||
|
const guildOwner = await client.users.fetch(interaction.guild.ownerId);
|
||||||
|
|
||||||
|
const addEmbed = new EmbedBuilder()
|
||||||
|
.setColor(interaction.member ? interaction.member.displayHexColor : 'NAVY')
|
||||||
|
.setTitle(interaction.guild.name)
|
||||||
|
.setThumbnail(interaction.guild.iconURL())
|
||||||
|
.addFields(
|
||||||
|
{ name: 'Number of users', value: (interaction.guild.memberCount - botCount).toString(), inline: true },
|
||||||
|
{ name: 'Number of bots', value: botCount.toString(), inline: true },
|
||||||
|
{ name: 'Total number of members', value: interaction.guild.memberCount.toString(), inline: true },
|
||||||
|
{ name: 'Number of channels', value: interaction.guild.channels.cache.size.toString(), inline: true },
|
||||||
|
{ name: '', value:'' },
|
||||||
|
{ name: 'Date when guild created', value: interaction.guild.createdAt.toString(), inline: true },
|
||||||
|
{ name: 'Owner', value: guildOwner.toString(), inline: true },
|
||||||
|
)
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
|
||||||
|
interaction.reply({ embeds: [addEmbed] });
|
||||||
|
|
||||||
|
},
|
||||||
|
};
|
|
@ -1,11 +1,12 @@
|
||||||
import { SlashCommandBuilder } from '@discordjs/builders';
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
import { MessageEmbed, version } from 'discord.js';
|
import { EmbedBuilder, version } from 'discord.js';
|
||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
.setName('stats')
|
.setName('stats')
|
||||||
.setDescription('Show some stats about the bot'),
|
.setDescription('Show some stats about the bot'),
|
||||||
|
category: 'utility',
|
||||||
async execute(interaction) {
|
async execute(interaction) {
|
||||||
const client = interaction.client;
|
const client = interaction.client;
|
||||||
const uptime = process.uptime();
|
const uptime = process.uptime();
|
||||||
|
@ -32,19 +33,21 @@ export default {
|
||||||
return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i];
|
return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i];
|
||||||
};
|
};
|
||||||
|
|
||||||
const statsEmbed = new MessageEmbed()
|
const statsEmbed = new EmbedBuilder()
|
||||||
.setColor(interaction.member ? interaction.member.displayHexColor : 'NAVY')
|
.setColor(interaction.member ? interaction.member.displayHexColor : 'Navy')
|
||||||
.setTitle('Bot stats')
|
.setTitle('Bot stats')
|
||||||
.setAuthor({ name: client.user.tag, iconURL: client.user.displayAvatarURL(), url: 'https://libtar.de' })
|
.setAuthor({ name: client.user.username, iconURL: client.user.displayAvatarURL(), url: 'https://libtar.de' })
|
||||||
.addField('Servers', client.guilds.cache.size.toString(), true)
|
.addFields(
|
||||||
.addField('Channels', client.channels.cache.size.toString(), true)
|
{ name: 'Servers', value: client.guilds.cache.size.toString(), inline: true },
|
||||||
.addField('Users', client.users.cache.size.toString(), true)
|
{ name: 'Channels', value: client.channels.cache.size.toString(), inline: true },
|
||||||
.addField('Ram usage', `${bytesToSize(process.memoryUsage().heapUsed)}/${bytesToSize(os.totalmem)}`, true)
|
{ name: 'Users', value: client.users.cache.size.toString(), inline: true },
|
||||||
.addField('CPU', `${os.cpus()[0].model} (${os.cpus().length} core)`, true)
|
{ name: 'Ram usage', value: `${bytesToSize(process.memoryUsage().heapUsed)}/${bytesToSize(os.totalmem)}`, inline: true },
|
||||||
.addField('OS', `${os.platform()} ${os.release()}`, true)
|
{ name: 'CPU', value: `${os.cpus()[0].model} (${os.cpus().length} core) (${os.arch()})`, inline: true },
|
||||||
.addField('Nodejs version', process.version, true)
|
{ name: 'OS', value: `${os.platform()} ${os.release()}`, inline: true },
|
||||||
.addField('Discord.js version', version, true)
|
{ name: 'Nodejs version', value: process.version, inline: true },
|
||||||
.addField('Uptime', dateString, true)
|
{ name: 'Discord.js version', value: version, inline: true },
|
||||||
|
{ name: 'Uptime', value: dateString, inline: true },
|
||||||
|
)
|
||||||
.setTimestamp();
|
.setTimestamp();
|
||||||
|
|
||||||
return interaction.reply({ embeds: [statsEmbed] });
|
return interaction.reply({ embeds: [statsEmbed] });
|
||||||
|
|
69
commands/utility/userinfo.js
Normal file
69
commands/utility/userinfo.js
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import { SlashCommandBuilder, EmbedBuilder } from 'discord.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('userinfo')
|
||||||
|
.setDescription('Show info about a user')
|
||||||
|
.addMentionableOption(option =>
|
||||||
|
option.setName('user')
|
||||||
|
.setDescription('Which user you want to see the info of?')
|
||||||
|
.setRequired(false)),
|
||||||
|
category: 'utility',
|
||||||
|
alias: ['user'],
|
||||||
|
async execute(interaction, args, client) {
|
||||||
|
await interaction.guild.members.fetch();
|
||||||
|
let member = interaction.member;
|
||||||
|
let user = interaction.user;
|
||||||
|
if (args.user) {
|
||||||
|
user = client.users.resolve(args.user);
|
||||||
|
member = interaction.guild.members.resolve(args.user);
|
||||||
|
}
|
||||||
|
const Embed = new EmbedBuilder()
|
||||||
|
.setColor(member ? member.displayHexColor : 'Navy')
|
||||||
|
.setAuthor({ name: `${user.username} (${user.id})`, iconURL: user.displayAvatarURL() })
|
||||||
|
.addFields(
|
||||||
|
{ name: 'Current rank hex color', value: member ? member.displayHexColor : 'No rank color', inline: true },
|
||||||
|
{ name: 'Joined guild at', value: member ? member.joinedAt.toString() : 'Not in this guild', inline: true },
|
||||||
|
{ name: 'Date when account created', value: user.createdAt.toString(), inline: true },
|
||||||
|
)
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
|
||||||
|
Embed.addFields({ name: '', value: '' });
|
||||||
|
|
||||||
|
// Show user status
|
||||||
|
/* Missing presence intent.
|
||||||
|
if (user.presence.activities[0]) {
|
||||||
|
Embed.addField('Presence', user.presence.activities[0], true);
|
||||||
|
if (user.presence.activities[0].details) Embed.addField('', user.presence.activities[0].details, true);
|
||||||
|
if (user.presence.activities[0].state) Embed.addField('', user.presence.activities[0].state, true);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// Is the user a bot?
|
||||||
|
if (user.bot) Embed.addFields({ name: 'Is a bot?', value: '✅', inline: true });
|
||||||
|
|
||||||
|
// Show on which platform they are using discord from if its not a bot
|
||||||
|
/* Missing presence intent.
|
||||||
|
if (user.presence.clientStatus && !user.bot) {
|
||||||
|
Embed.addFields({ name: '', value: '' });
|
||||||
|
if (user.presence.clientStatus.mobile) Embed.addFields({ name: 'Using discord on', value: '📱 ' + user.presence.clientStatus.mobile, inline: true });
|
||||||
|
if (user.presence.clientStatus.desktop) Embed.addFields({ name: 'Using discord on', value: '💻 ' + user.presence.clientStatus.desktop, inline: true });
|
||||||
|
if (user.presence.clientStatus.web) Embed.addFields({ name: 'Using discord on', value: '☁️ ' + user.presence.clientStatus.web, inline: true });
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (member) {
|
||||||
|
// Show since when this user have been boosting the current guild
|
||||||
|
if (member.premiumSince) Embed.addFields({ name: 'Boosting this guild since', value: member.premiumSince.toString(), inline: true });
|
||||||
|
// Show guild nickname
|
||||||
|
if (member.nickname) Embed.addFields({ name: 'Nickname', value: member.nickname, inline: true });
|
||||||
|
// Show member roles
|
||||||
|
if (member.roles) {
|
||||||
|
Embed.addFields({ name: 'Roles', value: `${[...member.roles.cache.values()].join(', ')}` });
|
||||||
|
Embed.addFields({ name: 'Permissions', value: `\`${member.permissions.toArray().join(', ')}\`` });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return interaction.reply({ embeds: [Embed] });
|
||||||
|
},
|
||||||
|
};
|
|
@ -1,10 +1,11 @@
|
||||||
import { SlashCommandBuilder } from '@discordjs/builders';
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
import utils from '../../utils/videos.js';
|
import utils from '../../utils/videos.js';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { exec } from 'node:child_process';
|
import { execFile } from 'node:child_process';
|
||||||
|
const { NODE_ENV } = process.env;
|
||||||
|
const ytdlpFormat = 'bestvideo[height<=?480]/best';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
|
@ -13,43 +14,101 @@ export default {
|
||||||
.addStringOption(option =>
|
.addStringOption(option =>
|
||||||
option.setName('url')
|
option.setName('url')
|
||||||
.setDescription('URL of the video you want to convert')
|
.setDescription('URL of the video you want to convert')
|
||||||
.setRequired(true)),
|
.setRequired(true))
|
||||||
async execute(interaction) {
|
.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 });
|
await interaction.deferReply({ ephemeral: false });
|
||||||
const url = interaction.options.getString('url');
|
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)) {
|
if (!await utils.stringIsAValidurl(url)) {
|
||||||
console.error(`Not a url!!! ${url}`);
|
console.error(`Not a url!!! ${url}`);
|
||||||
return interaction.editReply({ content: '❌ This does not look like a valid url!', ephemeral: true });
|
return interaction.editReply({ content: '❌ This does not look like a valid url!', ephemeral: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.downloadVideo(url, interaction.id)
|
const aproxFileSize = await utils.getVideoSize(url, ytdlpFormat);
|
||||||
|
console.log(aproxFileSize);
|
||||||
|
|
||||||
|
if (aproxFileSize > 4) {
|
||||||
|
return interaction.editReply('The file you are trying to convert is too big! Limit is 4 MB');
|
||||||
|
};
|
||||||
|
|
||||||
|
utils.downloadVideo(url, interaction.id, ytdlpFormat)
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
const file = fs.readdirSync(os.tmpdir()).filter(fn => fn.startsWith(interaction.id));
|
const file = fs.readdirSync(os.tmpdir()).filter(fn => fn.startsWith(interaction.id));
|
||||||
const output = `${os.tmpdir()}/${file}`;
|
let output = `${os.tmpdir()}/${file}`;
|
||||||
|
|
||||||
|
if (args.autocrop) {
|
||||||
|
const oldOutput = output;
|
||||||
|
output = `${os.tmpdir()}/autocrop${file}`;
|
||||||
|
await utils.autoCrop(oldOutput, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the fps of the original video
|
||||||
|
if (!args.fps) {
|
||||||
|
args.fps = getVideoFramerate(output);
|
||||||
|
}
|
||||||
|
|
||||||
const gifskiOutput = output.replace(path.extname(output), '.gif');
|
const gifskiOutput = output.replace(path.extname(output), '.gif');
|
||||||
const gifsicleOutput = output.replace(path.extname(output), 'gifsicle.gif');
|
const gifsicleOutput = output.replace(path.extname(output), 'gifsicle.gif');
|
||||||
|
|
||||||
// Extract every frame for gifski
|
// Extract every frame for gifski
|
||||||
await utils.ffmpeg(`-i ${output} ${os.tmpdir()}/frame${interaction.id}%04d.png`);
|
await utils.ffmpeg(['-i', output, `${os.tmpdir()}/frame${interaction.id}%04d.png`]);
|
||||||
// Make it look better
|
// Make it look better
|
||||||
await gifski(gifskiOutput, `${os.tmpdir()}/frame${interaction.id}*`);
|
await gifski(gifskiOutput, `${os.tmpdir()}/frame${interaction.id}*`, quality, await args.fps);
|
||||||
// Optimize it
|
// Optimize it
|
||||||
await gifsicle(gifskiOutput, gifsicleOutput);
|
await gifsicle(gifskiOutput, gifsicleOutput, args.noloop);
|
||||||
|
|
||||||
const fileStat = fs.statSync(gifsicleOutput);
|
const fileStat = fs.statSync(gifsicleOutput);
|
||||||
const fileSize = fileStat.size / 1000000.0;
|
const fileSize = fileStat.size / 1000000.0;
|
||||||
|
|
||||||
if (fileSize > 100) {
|
if (fileSize > 25) {
|
||||||
await interaction.deleteReply();
|
await interaction.deleteReply();
|
||||||
await interaction.followUp('❌ Uh oh! The video once converted is too big!', { ephemeral: true });
|
await interaction.followUp('❌ Uh oh! The video once converted is too big!', { ephemeral: true });
|
||||||
}
|
}
|
||||||
else if (fileSize > 8) {
|
else if (fileSize > maxFileSize) {
|
||||||
const fileURL = await utils.upload(gifsicleOutput)
|
const fileURL = await utils.upload(gifsicleOutput)
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
await interaction.editReply({ content: `ℹ️ File was bigger than 8 mb. It has been uploaded to an external site.\n${fileURL}`, ephemeral: false });
|
await interaction.editReply({ content: `ℹ️ File was bigger than ${maxFileSize} mb. It has been uploaded to an external site.\n${fileURL}`, ephemeral: false });
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
await interaction.editReply({ files: [gifsicleOutput], ephemeral: false });
|
await interaction.editReply({ files: [gifsicleOutput], ephemeral: false });
|
||||||
|
@ -58,30 +117,50 @@ export default {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
async function gifski(output, input) {
|
async function gifski(output, input, quality, fps) {
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
exec(`gifski --quality 70 -o ${output} ${input}`, (err, stdout, stderr) => {
|
// Shell: true should be fine as no user input is being passed
|
||||||
|
execFile('gifski', ['--quality', quality ? quality : 70, '--fps', fps ? fps : 20, '-o', output, input], { shell: true }, (err, stdout, stderr) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(stderr);
|
reject(stderr);
|
||||||
}
|
}
|
||||||
if (stderr) {
|
if (stderr) {
|
||||||
console.error(stderr);
|
console.error(stderr);
|
||||||
}
|
}
|
||||||
resolve(stdout);
|
console.log(NODE_ENV === 'development' ? stdout : null);
|
||||||
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function gifsicle(input, output) {
|
async function gifsicle(input, output, loop = false) {
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
exec(`gifsicle --colors 256 -i ${input} -o ${output}`, (err, stdout, stderr) => {
|
// Shell: true should be fine as no user input is being passed
|
||||||
|
execFile('gifsicle', ['--colors', '256', loop ? '--no-loopcount' : '', '-i', input, '-o', output], { shell: true }, (err, stdout, stderr) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(stderr);
|
reject(stderr);
|
||||||
}
|
}
|
||||||
if (stderr) {
|
if (stderr) {
|
||||||
console.error(stderr);
|
console.error(stderr);
|
||||||
}
|
}
|
||||||
resolve(stdout);
|
console.log(NODE_ENV === 'development' ? stdout : null);
|
||||||
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getVideoFramerate(input) {
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
execFile('ffprobe', ['-v', 'error', '-of', 'csv=p=0', '-select_streams', 'v:0', '-show_entries', 'stream=r_frame_rate', input], (err, stdout, stderr) => {
|
||||||
|
if (err) {
|
||||||
|
reject(stderr);
|
||||||
|
}
|
||||||
|
if (stderr) {
|
||||||
|
console.error(stderr);
|
||||||
|
}
|
||||||
|
const tempfps = stdout.trim().split('/');
|
||||||
|
const fps = tempfps[0] / tempfps[1];
|
||||||
|
return resolve(fps);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
43
commands/voice/dectalk.js
Normal file
43
commands/voice/dectalk.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
|
import { rand } from '../../utils/rand.js';
|
||||||
|
import { execFile } from 'node:child_process';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('dectalk')
|
||||||
|
.setDescription('Send a .wav sound file of what you wrote with dectalk')
|
||||||
|
.addStringOption(option =>
|
||||||
|
option.setName('message')
|
||||||
|
.setDescription('Write something so I can talk it back with dectalk.')
|
||||||
|
.setRequired(true)),
|
||||||
|
category: 'voice',
|
||||||
|
async execute(interaction, args) {
|
||||||
|
args.message = rand(args.message, interaction);
|
||||||
|
const output = `${interaction.id}_dectalk.wav`;
|
||||||
|
const message = '[:phoneme on]' + args.message;
|
||||||
|
await interaction.deferReply({ ephemeral: false });
|
||||||
|
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
// Untested, most likely do not work.
|
||||||
|
execFile('say.exe', ['-w', output, `${message}`], { cwd: './bin/dectalk/' }, (error, stdout, stderr) => {
|
||||||
|
sendMessage(output, error, stdout, stderr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (process.platform === 'linux' || process.platform === 'darwin') {
|
||||||
|
execFile('wine', ['say.exe', '-w', output, `${message}`], { cwd: './bin/dectalk/' }, (error, stdout, stderr) => {
|
||||||
|
sendMessage(`./bin/dectalk/${output}`, error, stdout, stderr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendMessage(file, error, stdout, stderr) {
|
||||||
|
if (error) {
|
||||||
|
console.error(stderr);
|
||||||
|
console.error(error);
|
||||||
|
return interaction.editReply('Oh no! an error has occurred!');
|
||||||
|
}
|
||||||
|
|
||||||
|
return interaction.editReply({ files: [file] });
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
};
|
14
config/config.json.example
Normal file
14
config/config.json.example
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"development": {
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"storage": "./database.sqlite3"
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"storage": ":memory"
|
||||||
|
},
|
||||||
|
"production": {
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"storage": "./database.sqlite3"
|
||||||
|
}
|
||||||
|
}
|
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",
|
||||||
|
},
|
||||||
|
}];
|
|
@ -1,17 +1,26 @@
|
||||||
import db from '../../models/index.js';
|
import db from '../../models/index.js';
|
||||||
const guildBlacklist = db.guildBlacklist;
|
const guildBlacklist = db.guildBlacklist;
|
||||||
import { MessageEmbed } from 'discord.js';
|
import { EmbedBuilder } from 'discord.js';
|
||||||
|
|
||||||
import dotenv from 'dotenv';
|
|
||||||
dotenv.config();
|
|
||||||
const { statusChannel, NODE_ENV } = process.env;
|
const { statusChannel, NODE_ENV } = process.env;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'guildCreate',
|
name: 'guildCreate',
|
||||||
once: true,
|
|
||||||
async execute(guild, client) {
|
async execute(guild, client) {
|
||||||
const guildOwner = await client.users.fetch(guild.ownerId);
|
const guildOwner = await client.users.fetch(guild.ownerId);
|
||||||
|
|
||||||
|
const isOptOut = await db.optout.findOne({ where: { userID: guildOwner.id } });
|
||||||
|
|
||||||
|
if (isOptOut) {
|
||||||
|
console.log(`A guild\n${guild.memberCount} users`);
|
||||||
|
if (statusChannel && NODE_ENV !== 'development') {
|
||||||
|
const channel = client.channels.resolve(statusChannel);
|
||||||
|
|
||||||
|
channel.send({ content: `An anonymous guild just added me.\nI'm now in ${client.guilds.cache.size} servers!` });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`${guild.name}\n${guild.memberCount} users\nOwner: ${guildOwner.username}\nOwner ID: ${guild.ownerId}`);
|
console.log(`${guild.name}\n${guild.memberCount} users\nOwner: ${guildOwner.username}\nOwner ID: ${guild.ownerId}`);
|
||||||
|
|
||||||
const blacklist = await guildBlacklist.findOne({ where: { guildID:guild.id } });
|
const blacklist = await guildBlacklist.findOne({ where: { guildID:guild.id } });
|
||||||
|
@ -25,16 +34,18 @@ export default {
|
||||||
const channel = client.channels.resolve(statusChannel);
|
const channel = client.channels.resolve(statusChannel);
|
||||||
const botCount = guild.members.cache.filter(member => member.user.bot).size;
|
const botCount = guild.members.cache.filter(member => member.user.bot).size;
|
||||||
console.log(guild.memberCount);
|
console.log(guild.memberCount);
|
||||||
const addEmbed = new MessageEmbed()
|
const addEmbed = new EmbedBuilder()
|
||||||
.setColor('#52e80d')
|
.setColor('#52e80d')
|
||||||
.setTitle('New boiz in town')
|
.setTitle('New boiz in town')
|
||||||
.setURL('https://www.youtube.com/watch?v=6n3pFFPSlW4')
|
.setURL('https://www.youtube.com/watch?v=6n3pFFPSlW4')
|
||||||
.setThumbnail(guild.iconURL())
|
.setThumbnail(guild.iconURL())
|
||||||
.addField('Guild', `${guild.name} (${guild.id})`)
|
.addFields(
|
||||||
.addField('Total number of members', guild.memberCount.toString(), true)
|
{ name: 'Guild', value: `${guild.name} (${guild.id})` },
|
||||||
.addField('Number of users', (guild.memberCount - botCount).toString(), true)
|
{ name: 'Total number of members', value: guild.memberCount.toString(), inline: true },
|
||||||
.addField('Number of bots', botCount.toString(), true)
|
{ name: 'Number of users', value: (guild.memberCount - botCount).toString(), inline: true },
|
||||||
.addField('Owner', `${guildOwner.username} (${guild.ownerId})`, true)
|
{ name: 'Number of bots', value: botCount.toString(), inline: true },
|
||||||
|
{ name: 'Owner', value: `${guildOwner.username} (${guild.ownerId})`, inline: true },
|
||||||
|
)
|
||||||
.setFooter({ text: `I'm now in ${client.guilds.cache.size} servers!` })
|
.setFooter({ text: `I'm now in ${client.guilds.cache.size} servers!` })
|
||||||
.setTimestamp();
|
.setTimestamp();
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,27 @@
|
||||||
import db from '../../models/index.js';
|
import db from '../../models/index.js';
|
||||||
const guildBlacklist = db.guildBlacklist;
|
const guildBlacklist = db.guildBlacklist;
|
||||||
import { MessageEmbed } from 'discord.js';
|
import { EmbedBuilder } from 'discord.js';
|
||||||
|
|
||||||
import dotenv from 'dotenv';
|
|
||||||
dotenv.config();
|
|
||||||
const { statusChannel, NODE_ENV } = process.env;
|
const { statusChannel, NODE_ENV } = process.env;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'guildDelete',
|
name: 'guildDelete',
|
||||||
once: true,
|
|
||||||
async execute(guild, client) {
|
async execute(guild, client) {
|
||||||
|
if (!guild.available) return;
|
||||||
const guildOwner = await client.users.fetch(guild.ownerId);
|
const guildOwner = await client.users.fetch(guild.ownerId);
|
||||||
|
|
||||||
|
const isOptOut = await db.optout.findOne({ where: { userID: guildOwner.id } });
|
||||||
|
|
||||||
|
if (isOptOut) {
|
||||||
|
console.log(`***BOT KICKED***A guild\n${guild.memberCount} users\n***BOT KICKED***`);
|
||||||
|
if (statusChannel && NODE_ENV !== 'development') {
|
||||||
|
const channel = client.channels.resolve(statusChannel);
|
||||||
|
|
||||||
|
channel.send({ content: `An anonymous guild just removed me.\nI'm now in ${client.guilds.cache.size} servers!` });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`***BOT KICKED***\n${guild.name}\n${guild.memberCount} users\nOwner: ${guildOwner.username}\nOwner ID: ${guild.ownerId}\n***BOT KICKED***`);
|
console.log(`***BOT KICKED***\n${guild.name}\n${guild.memberCount} users\nOwner: ${guildOwner.username}\nOwner ID: ${guild.ownerId}\n***BOT KICKED***`);
|
||||||
|
|
||||||
const blacklist = await guildBlacklist.findOne({ where: { guildID:guild.id } });
|
const blacklist = await guildBlacklist.findOne({ where: { guildID:guild.id } });
|
||||||
|
@ -21,16 +31,18 @@ export default {
|
||||||
const channel = client.channels.resolve(statusChannel);
|
const channel = client.channels.resolve(statusChannel);
|
||||||
const botCount = guild.members.cache.filter(member => member.user.bot).size;
|
const botCount = guild.members.cache.filter(member => member.user.bot).size;
|
||||||
console.log(guild.memberCount);
|
console.log(guild.memberCount);
|
||||||
const kickEmbed = new MessageEmbed()
|
const kickEmbed = new EmbedBuilder()
|
||||||
.setColor('#FF0000')
|
.setColor('#FF0000')
|
||||||
.setTitle('Some mofo just removed me from there guild :(')
|
.setTitle('Some mofo just removed me from there guild :(')
|
||||||
.setURL('https://www.youtube.com/watch?v=6n3pFFPSlW4')
|
.setURL('https://www.youtube.com/watch?v=6n3pFFPSlW4')
|
||||||
.setThumbnail(guild.iconURL())
|
.setThumbnail(guild.iconURL())
|
||||||
.addField('Guild', `${guild.name} (${guild.id})`)
|
.addFields(
|
||||||
.addField('Total number of members', guild.memberCount.toString(), true)
|
{ name: 'Guild', value: `${guild.name} (${guild.id})` },
|
||||||
.addField('Number of users', (guild.memberCount - botCount).toString(), true)
|
{ name: 'Total number of members', value: guild.memberCount.toString(), inline: true },
|
||||||
.addField('Number of bots', botCount.toString(), true)
|
{ name: 'Number of users', value: (guild.memberCount - botCount).toString(), inline: true },
|
||||||
.addField('Owner', `${guildOwner.username} (${guild.ownerId})`, true)
|
{ name: 'Number of bots', value: botCount.toString(), inline: true },
|
||||||
|
{ name: 'Owner', value: `${guildOwner.username} (${guild.ownerId})`, inline: true },
|
||||||
|
)
|
||||||
.setFooter({ text: `I'm now in ${client.guilds.cache.size} servers!` })
|
.setFooter({ text: `I'm now in ${client.guilds.cache.size} servers!` })
|
||||||
.setTimestamp();
|
.setTimestamp();
|
||||||
|
|
||||||
|
|
56
events/client/guildMemberAdd.js
Normal file
56
events/client/guildMemberAdd.js
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import db from '../../models/index.js';
|
||||||
|
import { rand } from '../../utils/rand.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'guildMemberAdd',
|
||||||
|
async execute(member, client) {
|
||||||
|
const isOptOut = await db.optout.findOne({ where: { userID: member.user.id } });
|
||||||
|
|
||||||
|
if (isOptOut) return;
|
||||||
|
|
||||||
|
const join = await db.joinChannel.findOne({ where: { guildID: member.guild.id } });
|
||||||
|
|
||||||
|
if (join) {
|
||||||
|
const channel = client.channels.resolve(join.get('channelID'));
|
||||||
|
|
||||||
|
let welcomeMessage = join.get('message');
|
||||||
|
|
||||||
|
const invite = new RegExp(/(https?:\/\/)?(www\.)?discord(?:app\.com|\.gg)[/invite/]?(?:(?!.*[Ii10OolL]).[a-zA-Z0-9]{5,6}|[a-zA-Z0-9-]{2,32})/g);
|
||||||
|
|
||||||
|
|
||||||
|
let username = member.user.username;
|
||||||
|
let user = member.user;
|
||||||
|
if (username.match(invite)) {
|
||||||
|
username = username.replace(/(https?:\/\/)?(www\.)?discord(?:app\.com|\.gg)[/invite/]?(?:(?!.*[Ii10OolL]).[a-zA-Z0-9]{5,6}|[a-zA-Z0-9-]{2,32})/g, '[REDACTED]');
|
||||||
|
user = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
welcomeMessage = welcomeMessage.replace(/\[member\]/g, username);
|
||||||
|
welcomeMessage = welcomeMessage.replace(/\[memberPing\]/g, user);
|
||||||
|
welcomeMessage = welcomeMessage.replace(/\[server\]/g, member.guild.name);
|
||||||
|
|
||||||
|
// add attachment
|
||||||
|
let attach;
|
||||||
|
if (welcomeMessage.includes('[attach:')) {
|
||||||
|
attach = welcomeMessage.split(/(\[attach:.*?])/);
|
||||||
|
for (let i = 0, l = attach.length; i < l; i++) {
|
||||||
|
if (attach[i].includes('[attach:')) {
|
||||||
|
attach = attach[i].replace('[attach:', '').slice(0, -1);
|
||||||
|
i = attach.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
welcomeMessage = welcomeMessage.replace(/(\[attach:.*?])/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
welcomeMessage = rand(welcomeMessage);
|
||||||
|
|
||||||
|
|
||||||
|
if (attach) {
|
||||||
|
return channel.send({ content: welcomeMessage, files: [attach] });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return channel.send({ content: welcomeMessage });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
56
events/client/guildMemberRemove.js
Normal file
56
events/client/guildMemberRemove.js
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import db from '../../models/index.js';
|
||||||
|
import { rand } from '../../utils/rand.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'guildMemberRemove',
|
||||||
|
async execute(member, client) {
|
||||||
|
const isOptOut = await db.optout.findOne({ where: { userID: member.user.id } });
|
||||||
|
|
||||||
|
if (isOptOut) return;
|
||||||
|
|
||||||
|
const leave = await db.leaveChannel.findOne({ where: { guildID: member.guild.id } });
|
||||||
|
|
||||||
|
if (leave) {
|
||||||
|
const channel = client.channels.resolve(leave.get('channelID'));
|
||||||
|
|
||||||
|
let welcomeMessage = leave.get('message');
|
||||||
|
|
||||||
|
const invite = new RegExp(/(https?:\/\/)?(www\.)?discord(?:app\.com|\.gg)[/invite/]?(?:(?!.*[Ii10OolL]).[a-zA-Z0-9]{5,6}|[a-zA-Z0-9-]{2,32})/g);
|
||||||
|
|
||||||
|
|
||||||
|
let username = member.user.username;
|
||||||
|
let user = member.user;
|
||||||
|
if (username.match(invite)) {
|
||||||
|
username = username.replace(/(https?:\/\/)?(www\.)?discord(?:app\.com|\.gg)[/invite/]?(?:(?!.*[Ii10OolL]).[a-zA-Z0-9]{5,6}|[a-zA-Z0-9-]{2,32})/g, '[REDACTED]');
|
||||||
|
user = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
welcomeMessage = welcomeMessage.replace(/\[member\]/g, username);
|
||||||
|
welcomeMessage = welcomeMessage.replace(/\[memberPing\]/g, user);
|
||||||
|
welcomeMessage = welcomeMessage.replace(/\[server\]/g, member.guild.name);
|
||||||
|
|
||||||
|
// add attachment
|
||||||
|
let attach;
|
||||||
|
if (welcomeMessage.includes('[attach:')) {
|
||||||
|
attach = welcomeMessage.split(/(\[attach:.*?])/);
|
||||||
|
for (let i = 0, l = attach.length; i < l; i++) {
|
||||||
|
if (attach[i].includes('[attach:')) {
|
||||||
|
attach = attach[i].replace('[attach:', '').slice(0, -1);
|
||||||
|
i = attach.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
welcomeMessage = welcomeMessage.replace(/(\[attach:.*?])/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
welcomeMessage = rand(welcomeMessage);
|
||||||
|
|
||||||
|
|
||||||
|
if (attach) {
|
||||||
|
return channel.send({ content: welcomeMessage, files: [attach] });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return channel.send({ content: welcomeMessage });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
|
@ -1,19 +1,28 @@
|
||||||
import { Permissions } from 'discord.js';
|
// TODO: Moving that to a dedicated function that works for both messages and interactions
|
||||||
import db from '../../models/index.js';
|
|
||||||
const ratelimit = {};
|
import { PermissionFlagsBits, InteractionType } from 'discord.js';
|
||||||
|
import db from '../../models/index.js';
|
||||||
|
import ratelimiter from '../../utils/ratelimiter.js';
|
||||||
|
|
||||||
import dotenv from 'dotenv';
|
|
||||||
dotenv.config();
|
|
||||||
const { ownerId } = process.env;
|
const { ownerId } = process.env;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'interactionCreate',
|
name: 'interactionCreate',
|
||||||
async execute(interaction) {
|
async execute(interaction) {
|
||||||
const client = interaction.client;
|
const client = interaction.client;
|
||||||
if (!interaction.isCommand()) return;
|
if (interaction.type !== InteractionType.ApplicationCommand) return;
|
||||||
|
|
||||||
const globalBlacklist = await db.Blacklists.findOne({ where: { type:'global', uid:interaction.user.id } });
|
const globalBlacklist = await db.Blacklists.findOne({ where: { type:'global', uid:interaction.user.id } });
|
||||||
const commandBlacklist = await db.Blacklists.findOne({ where: { type:interaction.commandName, uid:interaction.user.id } });
|
const commandBlacklist = await db.Blacklists.findOne({ where: { type:interaction.commandName, uid:interaction.user.id } });
|
||||||
|
|
||||||
|
if (interaction.guild) {
|
||||||
|
const serverBlacklist = await db.Blacklists.findOne({ where: { type:'guild', uid:interaction.guild.id } });
|
||||||
|
if (serverBlacklist) {
|
||||||
|
interaction.reply({ content: `This guild has been blacklisted for the following reason: \`${serverBlacklist.reason}\``, ephemeral: true });
|
||||||
|
return interaction.guild.leave();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (globalBlacklist) {
|
if (globalBlacklist) {
|
||||||
return interaction.reply({ content: `You are globally blacklisted for the following reason: \`${globalBlacklist.reason}\``, ephemeral: true });
|
return interaction.reply({ content: `You are globally blacklisted for the following reason: \`${globalBlacklist.reason}\``, ephemeral: true });
|
||||||
}
|
}
|
||||||
|
@ -21,7 +30,7 @@ export default {
|
||||||
return interaction.reply({ content: `You are blacklisted for the following reason: \`${commandBlacklist.reason}\``, ephemeral: true });
|
return interaction.reply({ content: `You are blacklisted for the following reason: \`${commandBlacklist.reason}\``, ephemeral: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
const userTag = interaction.user.tag;
|
const userTag = interaction.user.username;
|
||||||
const userID = interaction.user.id;
|
const userID = interaction.user.id;
|
||||||
const commandName = interaction.commandName;
|
const commandName = interaction.commandName;
|
||||||
|
|
||||||
|
@ -29,56 +38,91 @@ export default {
|
||||||
|
|
||||||
if (!command) return;
|
if (!command) return;
|
||||||
|
|
||||||
console.log(`\x1b[33m${userTag} (${userID})\x1b[0m launched command \x1b[33m${commandName}\x1b[0m`);
|
let isOptOut = await db.optout.findOne({ where: { userID: interaction.user.id } });
|
||||||
|
|
||||||
|
if (commandName === 'optout') {
|
||||||
|
isOptOut = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestamp = new Date();
|
||||||
|
console.log(`[${timestamp.toISOString()}] \x1b[33m${ isOptOut ? 'A user' : `${userTag} (${userID})`}\x1b[0m launched command \x1b[33m${commandName}\x1b[0m using slash`);
|
||||||
|
|
||||||
|
|
||||||
// Owner only check
|
// Owner only check
|
||||||
if (command.ownerOnly && interaction.user.id !== ownerId) {
|
if (command.ownerOnly && interaction.user.id !== ownerId) {
|
||||||
return interaction.reply({ content: '❌ This command is reserved for the owner!', ephemeral: true });
|
return interaction.reply({ content: '❌ This command is reserved for the owner!', ephemeral: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Guild only check
|
||||||
|
if (command.guildOnly && !interaction.guild) {
|
||||||
|
return interaction.reply({ content: '❌ This command only work in a server!', ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the bot has the needed permissions
|
// Check if the bot has the needed permissions
|
||||||
if (command.clientPermissions) {
|
if (command.default_permission) {
|
||||||
const clientMember = await interaction.guild.members.fetch(client.user.id);
|
const clientMember = await interaction.guild.members.fetch(client.user.id);
|
||||||
if (!clientMember.permissions.has(command.clientPermissions)) {
|
if (!clientMember.permissions.has(command.clientPermissions)) {
|
||||||
return interaction.reply({ content: `❌ I am missing one of the following permission(s): \`${new Permissions(command.clientPermissions).toArray()}\``, ephemeral: true });
|
return interaction.reply({ content: `❌ I am missing one of the following permission(s): \`${new PermissionFlagsBits(command.clientPermissions).toArray()}\``, ephemeral: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the user has the needed permissions
|
// Check if the user has the needed permissions
|
||||||
if (command.userPermissions) {
|
/*
|
||||||
|
if (command.default_member_permissions) {
|
||||||
if (!interaction.member.permissions.has(command.userPermissions)) {
|
if (!interaction.member.permissions.has(command.userPermissions)) {
|
||||||
return interaction.reply({ content: `❌ You are missing one of the following permission(s): \`${new Permissions(command.userPermissions).toArray()}\``, ephemeral: true });
|
return interaction.reply({ content: `❌ You are missing one of the following permission(s): \`${new PermissionFlagsBits(command.userPermissions).toArray()}\``, ephemeral: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Check if the limit of parallel execution has been reached
|
||||||
|
if (command.parallelLimit) {
|
||||||
|
const doParallelLimit = await ratelimiter.checkParallel(interaction.user, commandName, command);
|
||||||
|
if (doParallelLimit.limited) {
|
||||||
|
return await interaction.reply({ content: doParallelLimit.msg, ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
ratelimiter.addParallel(commandName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the ratelimit
|
||||||
|
const doRateLimit = await ratelimiter.check(interaction.user, commandName, command);
|
||||||
|
if (doRateLimit) {
|
||||||
|
return interaction.reply({ content: doRateLimit, ephemeral: true });
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const date = new Date();
|
interaction.prefix = '/';
|
||||||
if (ratelimit[userID]) {
|
|
||||||
if (ratelimit[userID].cooldown) {
|
|
||||||
if (commandName === ratelimit[userID].command && date > ratelimit[userID].cooldown) {
|
|
||||||
ratelimit[userID].limit = 0;
|
|
||||||
ratelimit[userID].cooldown = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (commandName === ratelimit[userID].command && command.ratelimit === ratelimit[userID].limit) {
|
|
||||||
return await interaction.reply({ content: `You are being rate limited. You can try again in ${Math.floor((ratelimit[userID].cooldown - date) / 1000)} seconds.`, ephemeral: true });
|
|
||||||
|
|
||||||
|
const args = {};
|
||||||
|
// https://discord-api-types.dev/api/discord-api-types-v10/enum/ApplicationCommandOptionType
|
||||||
|
interaction.options.data.forEach(arg => {
|
||||||
|
let payload = arg.value;
|
||||||
|
if (arg.type === 9) {
|
||||||
|
payload = arg.member;
|
||||||
}
|
}
|
||||||
|
else if (arg.type === 11) {
|
||||||
|
payload = arg.attachment;
|
||||||
|
}
|
||||||
|
args[arg.name] = payload;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isOptOut) {
|
||||||
|
console.log(`[${timestamp.toISOString()}] \x1b[33m⤷\x1b[0m with args ${JSON.stringify(args)}`);
|
||||||
}
|
}
|
||||||
if (command.ratelimit) {
|
|
||||||
ratelimit[userID] = { command: commandName, limit: ratelimit[userID] ? ratelimit[userID].limit + 1 : 1 };
|
|
||||||
if (command.ratelimit === ratelimit[userID].limit) {
|
|
||||||
date.setSeconds(date.getSeconds() + command.cooldown);
|
|
||||||
|
|
||||||
ratelimit[userID] = { command: commandName, limit: ratelimit[userID].limit, cooldown: date };
|
await command.execute(interaction, args, client)
|
||||||
}
|
.then(async () => {
|
||||||
}
|
const hasPrallelLimit = await ratelimiter.checkParallel(interaction.user, commandName, command);
|
||||||
await command.execute(interaction);
|
if (hasPrallelLimit) ratelimiter.removeParallel(commandName);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
await interaction.followUp({ content: 'There was an error while executing this command!', ephemeral: true });
|
const hasPrallelLimit = await ratelimiter.checkParallel(interaction.user, commandName, command);
|
||||||
|
if (hasPrallelLimit) ratelimiter.removeParallel(commandName);
|
||||||
|
await interaction.followUp({ content: `There was an error while executing this command!\n\`${error}\``, ephemeral: true });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
471
events/client/messageCreate.js
Normal file
471
events/client/messageCreate.js
Normal file
|
@ -0,0 +1,471 @@
|
||||||
|
/* TODO:
|
||||||
|
* Make this shit work.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ApplicationCommandOptionType, EmbedBuilder, PermissionFlagsBits } from 'discord.js';
|
||||||
|
import db from '../../models/index.js';
|
||||||
|
import { rand } from '../../utils/rand.js';
|
||||||
|
import ratelimiter from '../../utils/ratelimiter.js';
|
||||||
|
|
||||||
|
const { ownerId, prefix } = process.env;
|
||||||
|
const prefixs = prefix.split(',');
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'messageCreate',
|
||||||
|
async execute(message, client) {
|
||||||
|
if (message.partials) {
|
||||||
|
await message.fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.author.bot) return;
|
||||||
|
|
||||||
|
/* Autoresponse feature & tag
|
||||||
|
*
|
||||||
|
* This section contains autoresponse and tag feature
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// auto responses
|
||||||
|
if (message.guild) {
|
||||||
|
const autoresponseStat = await db.autoresponseStat.findOne({ where: { serverID: message.guild.id, stat: 'enable' } });
|
||||||
|
if (autoresponseStat) {
|
||||||
|
// Infinite haha very yes
|
||||||
|
if (message.content.toLowerCase().startsWith('haha very') && message.content.toLowerCase().endsWith('yes')) {
|
||||||
|
let yes = message.content.toLowerCase().replace('haha', '');
|
||||||
|
yes = yes.replace('yes', '');
|
||||||
|
yes += 'very';
|
||||||
|
return message.channel.send(`haha${yes} yes`);
|
||||||
|
}
|
||||||
|
else if (message.content.toLowerCase() == 'haha yes') {
|
||||||
|
return message.channel.send('haha very yes');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reply with images as attachement
|
||||||
|
const autoresponse = await db.autoresponse.findOne({ where: { trigger: message.content.toLowerCase() } });
|
||||||
|
|
||||||
|
if (autoresponse) {
|
||||||
|
db.autoresponse.findOne({ where: { trigger: message.content.toLowerCase() } });
|
||||||
|
const trigger = autoresponse.get('trigger');
|
||||||
|
const type = autoresponse.get('type');
|
||||||
|
const content = autoresponse.get('response');
|
||||||
|
|
||||||
|
if (trigger == message.content.toLowerCase() && type == 'text') {
|
||||||
|
return message.channel.send(content);
|
||||||
|
}
|
||||||
|
else if (trigger == message.content.toLowerCase() && type == 'react') {
|
||||||
|
return message.react(content);
|
||||||
|
}
|
||||||
|
else if (trigger == message.content.toLowerCase() && type == 'image') {
|
||||||
|
return message.channel.send({ files: [content] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// User autoresponse
|
||||||
|
const tag = await db.Tag.findOne({ where: { trigger: message.content.toLowerCase(), serverID: message.guild.id } });
|
||||||
|
if (tag) {
|
||||||
|
db.Tag.findOne({ where: { trigger: message.content.toLowerCase(), serverID: message.guild.id } });
|
||||||
|
let text = tag.get('response');
|
||||||
|
/*
|
||||||
|
if (text.includes('[ban]')) {
|
||||||
|
message.member.ban('Tag ban :^)');
|
||||||
|
}
|
||||||
|
else if (text.includes('[kick]')) {
|
||||||
|
message.member.kick('Tag kick :^)');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
*/
|
||||||
|
if (text.includes('[delete]')) {
|
||||||
|
message.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
text = rand(text, message);
|
||||||
|
|
||||||
|
let attach = '';
|
||||||
|
|
||||||
|
if (text.includes('[attach:')) {
|
||||||
|
attach = text.split(/(\[attach:.*?])/);
|
||||||
|
for (let i = 0, l = attach.length; i < l; i++) {
|
||||||
|
if (attach[i].includes('[attach:')) {
|
||||||
|
attach = attach[i].replace('[attach:', '').slice(0, -1);
|
||||||
|
i = attach.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text = text.replace(/(\[attach:.*?])/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// THIS SECTION IS VERY VERY BAD MUST CHANGE
|
||||||
|
if (text.includes('[embed]')) {
|
||||||
|
text = text.replace(/\[embed\]/, ' ');
|
||||||
|
|
||||||
|
let title = '';
|
||||||
|
let desc = '';
|
||||||
|
let image;
|
||||||
|
let thumbnail;
|
||||||
|
let footer = '';
|
||||||
|
let color;
|
||||||
|
|
||||||
|
if (text.includes('[embedImage:')) {
|
||||||
|
image = text.split(/(\[embedImage:.*?])/);
|
||||||
|
|
||||||
|
for (let i = 0, l = image.length; i < l; i++) {
|
||||||
|
if (image[i].includes('[embedImage:')) {
|
||||||
|
image = image[i].replace('[embedImage:', '').slice(0, -1);
|
||||||
|
text = text.replace(/(\[embedimage:.*?])/g, '');
|
||||||
|
i = image.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text.includes('[embedThumbnail:')) {
|
||||||
|
thumbnail = text.split(/(\[embedThumbnail:.*?])/);
|
||||||
|
|
||||||
|
for (let i = 0, l = thumbnail.length; i < l; i++) {
|
||||||
|
if (thumbnail[i].includes('[embedThumbnail:')) {
|
||||||
|
thumbnail = thumbnail[i].replace('[embedThumbnail:', '').slice(0, -1);
|
||||||
|
text = text.replace(/(\[embedThumbnail:.*?])/g, '');
|
||||||
|
i = thumbnail.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text.includes('[embedColor:')) {
|
||||||
|
color = text.split(/(\[embedColor:.*?])/);
|
||||||
|
for (let i = 0, l = color.length; i < l; i++) {
|
||||||
|
if (color[i].includes('[embedColor:')) {
|
||||||
|
color = color[i].replace('[embedColor:', '').slice(0, -1);
|
||||||
|
text = text.replace(/(\[embedColor:.*?])/g, '');
|
||||||
|
i = color.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (text.includes('[embedTitle:')) {
|
||||||
|
title = text.split(/(\[embedTitle:.*?])/);
|
||||||
|
for (let i = 0, l = title.length; i < l; i++) {
|
||||||
|
if (title[i].includes('[embedTitle:')) {
|
||||||
|
title = title[i].replace('[embedTitle:', '').slice(0, -1);
|
||||||
|
text = text.replace(/(\[embedTitle:.*?])/g, '');
|
||||||
|
i = title.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text.includes('[embedFooter:')) {
|
||||||
|
footer = text.split(/(\[embedFooter:.*?])/);
|
||||||
|
for (let i = 0, l = footer.length; i < l; i++) {
|
||||||
|
if (footer[i].includes('[embedFooter:')) {
|
||||||
|
footer = footer[i].replace('[embedFooter:', '').slice(0, -1);
|
||||||
|
text = text.replace(/(\[embedFooter:.*?])/g, '');
|
||||||
|
i = footer.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text.includes('[embedDesc:')) {
|
||||||
|
desc = text.split(/(\[embedDesc:.*?])/);
|
||||||
|
for (let i = 0, l = desc.length; i < l; i++) {
|
||||||
|
if (desc[i].includes('[embedDesc:')) {
|
||||||
|
desc = desc[i].replace('[embedDesc:', '').slice(0, -1);
|
||||||
|
i = desc.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setColor(color)
|
||||||
|
.setTitle(title)
|
||||||
|
.setImage(image)
|
||||||
|
.setThumbnail(thumbnail)
|
||||||
|
.setDescription(desc)
|
||||||
|
.setFooter(footer)
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
|
||||||
|
if (attach) {
|
||||||
|
return message.channel.send(embed, { files: [attach] });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return message.channel.send(embed);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (attach) {
|
||||||
|
return message.channel.send(text, { files: [attach] });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return message.channel.send(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Quotation feature
|
||||||
|
*
|
||||||
|
* This section will contain the code for the quotation feature, it will detect link for it and send it as embed
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const isOptOut = await db.optout.findOne({ where: { userID: message.author.id } });
|
||||||
|
if (!isOptOut) {
|
||||||
|
const quotationstat = await db.quotationStat.findOne({ where: { serverID: message.guild.id, stat: 'enable' } });
|
||||||
|
|
||||||
|
if (quotationstat && (message.content.includes('discordapp.com/channels/') || message.content.includes('discord.com/channels/'))) {
|
||||||
|
const url = message.content.split('/');
|
||||||
|
const guildID = url[4];
|
||||||
|
const channelID = url[5];
|
||||||
|
const messageID = url[6].split(' ')[0];
|
||||||
|
|
||||||
|
|
||||||
|
// Verify if the guild, channel and message exist
|
||||||
|
const guild = client.guilds.resolve(guildID);
|
||||||
|
if (!guild) return;
|
||||||
|
const channel = client.channels.resolve(channelID);
|
||||||
|
if (!channel) return;
|
||||||
|
const quote = await channel.messages.fetch(messageID)
|
||||||
|
.catch(() => {
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
if (!quote) return;
|
||||||
|
|
||||||
|
const Embed = new EmbedBuilder()
|
||||||
|
.setAuthor({ name: quote.author.username, iconURL: quote.author.displayAvatarURL() })
|
||||||
|
.setColor(message.member ? message.member.displayHexColor : 'Navy')
|
||||||
|
.addFields(
|
||||||
|
{ name: 'Jump to', value: `[message](https://discordapp.com/channels/${message.guild.id}/${channelID}/${messageID})`, inline: true },
|
||||||
|
{ name: 'In channel', value: quote.channel.name.toString(), inline: true },
|
||||||
|
{ name: 'Quoted by', value: message.author.toString(), inline: true },
|
||||||
|
)
|
||||||
|
.setDescription(quote.content)
|
||||||
|
.setTimestamp(quote.createdTimestamp);
|
||||||
|
|
||||||
|
if (quote.member) Embed.setAuthor({ name: `${quote.author.username}#${quote.author.discriminator}`, iconURL: quote.author.displayAvatarURL() });
|
||||||
|
|
||||||
|
if (quote.author.bot) Embed.setAuthor({ name: `${quote.author.username}#${quote.author.discriminator} (BOT)`, iconURL: quote.author.displayAvatarURL() });
|
||||||
|
|
||||||
|
if (guild.id != message.guild.id) Embed.addFields({ name: 'In guild', value: guild.name, inline: true });
|
||||||
|
const Attachment = Array.from(message.attachments.values());
|
||||||
|
if (Attachment[0]) Embed.setImage(Attachment[0].url);
|
||||||
|
|
||||||
|
return message.channel.send({ embeds: [Embed] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command handling from message
|
||||||
|
// TODO: Moving that to a dedicated function that works for both messages and interactions
|
||||||
|
|
||||||
|
let hasPrefix = false;
|
||||||
|
prefixs.forEach(p => {
|
||||||
|
if (message.content.toLowerCase().startsWith(p)) {
|
||||||
|
hasPrefix = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!hasPrefix) return;
|
||||||
|
|
||||||
|
const messageArray = message.content.match(/"[^"]*"|\S+/g).map(m => m.slice(0, 1) === '"' ? m.slice(1, -1) : m);
|
||||||
|
let commandName = messageArray[1].toLowerCase();
|
||||||
|
const messageArgs = messageArray.splice(2, messageArray.length);
|
||||||
|
|
||||||
|
// Search for alias
|
||||||
|
client.commands.find(c => {
|
||||||
|
if (c.alias) {
|
||||||
|
if (c.alias.includes(commandName)) {
|
||||||
|
commandName = c.data.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const command = client.commands.get(commandName);
|
||||||
|
|
||||||
|
if (!command) return;
|
||||||
|
|
||||||
|
const globalBlacklist = await db.Blacklists.findOne({ where: { type:'global', uid:message.author.id } });
|
||||||
|
const commandBlacklist = await db.Blacklists.findOne({ where: { type:commandName, uid:message.author.id } });
|
||||||
|
|
||||||
|
if (message.guild) {
|
||||||
|
const serverBlacklist = await db.Blacklists.findOne({ where: { type:'guild', uid:message.guild.id } });
|
||||||
|
if (serverBlacklist) {
|
||||||
|
message.reply({ content: `This guild has been blacklisted for the following reason: \`${serverBlacklist.reason}\``, ephemeral: true });
|
||||||
|
return message.guild.leave();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (globalBlacklist) {
|
||||||
|
return message.reply({ content: `You are globally blacklisted for the following reason: \`${globalBlacklist.reason}\``, ephemeral: true });
|
||||||
|
}
|
||||||
|
else if (commandBlacklist) {
|
||||||
|
return message.reply({ content: `You are blacklisted for the following reason: \`${commandBlacklist.reason}\``, ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const userTag = message.author.username;
|
||||||
|
const userID = message.author.id;
|
||||||
|
|
||||||
|
let isOptOut = await db.optout.findOne({ where: { userID: message.author.id } });
|
||||||
|
|
||||||
|
if (commandName === 'optout') {
|
||||||
|
isOptOut = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestamp = new Date();
|
||||||
|
console.log(`[${timestamp.toISOString()}] \x1b[33m${ isOptOut ? 'A user' : `${userTag} (${userID})`}\x1b[0m launched command \x1b[33m${commandName}\x1b[0m using prefix`);
|
||||||
|
|
||||||
|
|
||||||
|
// Owner only check
|
||||||
|
if (command.ownerOnly && message.author.id !== ownerId) {
|
||||||
|
return message.reply({ content: '❌ This command is reserved for the owner!', ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guild only check
|
||||||
|
if (command.guildOnly && !message.guild) {
|
||||||
|
return message.reply({ content: '❌ This command only work in a server!', ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the bot has the needed permissions
|
||||||
|
if (command.clientPermissions) {
|
||||||
|
const clientMember = await message.guild.members.fetch(client.user.id);
|
||||||
|
if (!clientMember.permissions.has(command.clientPermissions)) {
|
||||||
|
return message.reply({ content: `❌ I am missing one of the following permission(s): \`${new PermissionFlagsBits(command.clientPermissions).toArray()}\``, ephemeral: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the user has the needed permissions
|
||||||
|
if (command.default_member_permissions) {
|
||||||
|
if (!message.member.permissions.has(command.default_member_permissions)) {
|
||||||
|
return message.reply({ content: `❌ You are missing one of the following permission(s): \`${new PermissionFlagsBits(command.userPermissions).toArray()}\``, ephemeral: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the limit of parallel execution has been reached
|
||||||
|
if (command.parallelLimit) {
|
||||||
|
const doParallelLimit = await ratelimiter.checkParallel(message.author, commandName, command);
|
||||||
|
if (doParallelLimit.limited) {
|
||||||
|
return await message.reply({ content: doParallelLimit.msg, ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
ratelimiter.addParallel(commandName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the ratelimit
|
||||||
|
const doRateLimit = await ratelimiter.check(message.author, commandName, command);
|
||||||
|
if (doRateLimit) {
|
||||||
|
return message.reply({ content: doRateLimit, ephemeral: true });
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
message.user = message.author;
|
||||||
|
message.isMessage = true;
|
||||||
|
message.prefix = `${messageArray[0]} `;
|
||||||
|
|
||||||
|
let waitingmsg;
|
||||||
|
const toDelete = [];
|
||||||
|
message.deferReply = async function() {
|
||||||
|
waitingmsg = await message.reply('The bot is thinking...');
|
||||||
|
};
|
||||||
|
message.followUp = async function(payload) {
|
||||||
|
if (payload.ephemeral) {
|
||||||
|
toDelete.push(await message.channel.send(payload));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await message.channel.send(payload);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
message.editReply = async function(payload) {
|
||||||
|
if (waitingmsg) {
|
||||||
|
await waitingmsg.delete();
|
||||||
|
}
|
||||||
|
await message.channel.send(payload);
|
||||||
|
};
|
||||||
|
message.deleteReply = async function() {
|
||||||
|
if (waitingmsg) {
|
||||||
|
await waitingmsg.delete()
|
||||||
|
.catch(() => { return; });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
message.cleanUp = async function() {
|
||||||
|
toDelete.forEach(async msg => {
|
||||||
|
msg.delete();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const args = {};
|
||||||
|
|
||||||
|
let argsToDelete = 0;
|
||||||
|
command.data.options.forEach(obj => {
|
||||||
|
if (obj.type === ApplicationCommandOptionType.Attachment) {
|
||||||
|
args[obj.name] = message.attachments.first();
|
||||||
|
delete command.data.options[command.data.options.indexOf(obj)];
|
||||||
|
argsToDelete++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const argsLength = command.data.options.length - argsToDelete;
|
||||||
|
const missingRequired = [];
|
||||||
|
|
||||||
|
for (let i = 0, j = 0; i < argsLength; i++, j++) {
|
||||||
|
const arg = command.data.options[j];
|
||||||
|
|
||||||
|
if (arg.required && !messageArgs[i]) {
|
||||||
|
missingRequired.push({ name: arg.name, description: arg.description });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!messageArgs[i]) continue;
|
||||||
|
|
||||||
|
if (arg.type === ApplicationCommandOptionType.Attachment) continue;
|
||||||
|
|
||||||
|
let payloadName = arg.name;
|
||||||
|
let payload = messageArgs[i];
|
||||||
|
|
||||||
|
if (i >= argsLength - 1) {
|
||||||
|
payload = messageArgs.slice(i).join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg.type === ApplicationCommandOptionType.Boolean && !messageArgs[i].startsWith('--')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (messageArgs[i].startsWith('--')) {
|
||||||
|
payloadName = payload.substring(2);
|
||||||
|
payload = true;
|
||||||
|
j--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg.type === ApplicationCommandOptionType.Mentionable || arg.type === ApplicationCommandOptionType.User) {
|
||||||
|
await message.guild.members.fetch();
|
||||||
|
payload = message.mentions.members.first() ? message.mentions.members.first() : message.guild.members.cache.find(u => u.user.username.toLowerCase().includes(payload.toLowerCase()));
|
||||||
|
}
|
||||||
|
|
||||||
|
args[payloadName] = payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isOptOut && argsLength > 0) {
|
||||||
|
console.log(`[${timestamp.toISOString()}] \x1b[33m⤷\x1b[0m with args ${JSON.stringify(args)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missingRequired.length > 0) {
|
||||||
|
let missingMsg = '';
|
||||||
|
missingRequired.forEach(arg => {
|
||||||
|
missingMsg += `${arg.name} | ${arg.description}\n`;
|
||||||
|
});
|
||||||
|
return message.reply(`You are missing a required argument!\n\`${missingMsg}\``);
|
||||||
|
}
|
||||||
|
|
||||||
|
await command.execute(message, args, client)
|
||||||
|
.then(async () => {
|
||||||
|
const hasPrallelLimit = await ratelimiter.checkParallel(message.author, commandName, command);
|
||||||
|
if (hasPrallelLimit) ratelimiter.removeParallel(commandName);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
const hasPrallelLimit = await ratelimiter.checkParallel(message.author, commandName, command);
|
||||||
|
if (hasPrallelLimit) ratelimiter.removeParallel(commandName);
|
||||||
|
await message.reply({ content: `There was an error while executing this command!\n\`${error}\`` })
|
||||||
|
.catch(async () => {
|
||||||
|
await message.channel.send({ content: `There was an error while executing this command!\n\`${error}\`` });
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
171
events/client/messageReactionAdd.js
Normal file
171
events/client/messageReactionAdd.js
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
import { EmbedBuilder } from 'discord.js';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import db from '../../models/index.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'messageReactionAdd',
|
||||||
|
async execute(reaction, users, c) {
|
||||||
|
if (reaction.partial) {
|
||||||
|
await reaction.fetch()
|
||||||
|
.catch(err => {
|
||||||
|
return console.error(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* I don't really know why this is causing issues.
|
||||||
|
if (reaction.message.partial) {
|
||||||
|
await reaction.message.fetch()
|
||||||
|
.catch(err => {
|
||||||
|
return console.error(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
const isOptOut = await db.optout.findOne({ where: { userID: reaction.message.author.id } });
|
||||||
|
if (isOptOut) return;
|
||||||
|
|
||||||
|
let starboardChannel, shameboardChannel;
|
||||||
|
let reactionCount = reaction.count;
|
||||||
|
|
||||||
|
// If one of the reaction is the author of the message remove 1 to the reaction count
|
||||||
|
reaction.users.cache.forEach(user => {
|
||||||
|
if (reaction.message.author == user) reactionCount--;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Starboard
|
||||||
|
if (fs.existsSync(`./json/board/star${reaction.message.guild.id}.json`)) {
|
||||||
|
starboardChannel = JSON.parse(fs.readFileSync(`./json/board/star${reaction.message.guild.id}.json`));
|
||||||
|
let staremote = starboardChannel.emote;
|
||||||
|
const starcount = starboardChannel.count;
|
||||||
|
// Get name of the custom emoji
|
||||||
|
if (reaction.message.guild.emojis.resolve(staremote.replace(/\D/g, ''))) {
|
||||||
|
staremote = reaction.message.guild.emojis.resolve(staremote.replace(/\D/g, ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reaction.emoji == staremote || reaction.emoji.name == staremote) {
|
||||||
|
if (global.boards[reaction.message.id] && reactionCount > starcount) {
|
||||||
|
return editEmbed('starboard', staremote, global.boards[reaction.message.id]);
|
||||||
|
}
|
||||||
|
else if (reactionCount == starcount) {
|
||||||
|
return sendEmbed('starboard', staremote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shameboard
|
||||||
|
if (fs.existsSync(`./json/board/shame${reaction.message.guild.id}.json`)) {
|
||||||
|
shameboardChannel = JSON.parse(fs.readFileSync(`./json/board/shame${reaction.message.guild.id}.json`));
|
||||||
|
let shameemote = shameboardChannel.emote;
|
||||||
|
const shamecount = shameboardChannel.count;
|
||||||
|
// Get name of the custom emoji
|
||||||
|
if (reaction.message.guild.emojis.resolve(shameemote.replace(/\D/g, ''))) {
|
||||||
|
shameemote = reaction.message.guild.emojis.resolve(shameemote.replace(/\D/g, ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reaction.emoji == shameemote || reaction.emoji.name == shameemote) {
|
||||||
|
if (global.boards[reaction.message.id] && reactionCount > shamecount) {
|
||||||
|
return editEmbed('shameboard', shameemote, global.boards[reaction.message.id]);
|
||||||
|
}
|
||||||
|
else if (reactionCount == shamecount) {
|
||||||
|
return sendEmbed('shameboard', shameemote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function editEmbed(name, emote, boardID) {
|
||||||
|
let channel;
|
||||||
|
if (name == 'starboard') {
|
||||||
|
channel = c.channels.resolve(starboardChannel.starboard);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
channel = c.channels.resolve(shameboardChannel.shameboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = await channel.messages.resolve(boardID);
|
||||||
|
// If the message doesn't have embeds assume it got deleted so don't do anything
|
||||||
|
if (!message) return;
|
||||||
|
|
||||||
|
// If the original embed description is empty make this embed null ( and not empty )
|
||||||
|
let description = message.embeds[0].description;
|
||||||
|
if (!message.embeds[0].description || message.embeds[0].description == undefined) {
|
||||||
|
description = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Embed = new EmbedBuilder()
|
||||||
|
.setColor(reaction.message.member ? reaction.message.member.displayHexColor : 'Navy')
|
||||||
|
.setAuthor({ name: reaction.message.author.username, iconURL: reaction.message.author.displayAvatarURL() })
|
||||||
|
.addFields(
|
||||||
|
{ name: 'Jump to', value: `[message](https://discordapp.com/channels/${reaction.message.guild.id}/${reaction.message.channel.id}/${reaction.message.id})`, inline: true },
|
||||||
|
{ name: 'Channel', value: reaction.message.channel.toString(), inline: true },
|
||||||
|
)
|
||||||
|
.setDescription(description)
|
||||||
|
.setFooter({ text: `${emote} ${reactionCount}` })
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
if (reaction.message.guild.emojis.resolve(emote)) Embed.setFooter(reactionCount, reaction.message.guild.emojis.resolve(emote).url);
|
||||||
|
|
||||||
|
message.edit({ embeds: [Embed] });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendEmbed(name, emote) {
|
||||||
|
let messageAttachments = reaction.message.attachments.map(u => u.url)[0];
|
||||||
|
// Should change this so it automatically pick the channel ( I'm lazy right now )
|
||||||
|
let channel;
|
||||||
|
if (name == 'starboard') {
|
||||||
|
channel = c.channels.resolve(starboardChannel.starboard);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
channel = c.channels.resolve(shameboardChannel.shameboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Embed = new EmbedBuilder()
|
||||||
|
.setColor(reaction.message.member ? reaction.message.member.displayHexColor : 'Navy')
|
||||||
|
.setAuthor({ name: reaction.message.author.username, iconURL: reaction.message.author.displayAvatarURL() })
|
||||||
|
.addFields(
|
||||||
|
{ name: 'Jump to', value: `[message](https://discordapp.com/channels/${reaction.message.guild.id}/${reaction.message.channel.id}/${reaction.message.id})`, inline: true },
|
||||||
|
{ name: 'Channel', value: reaction.message.channel.toString(), inline: true },
|
||||||
|
)
|
||||||
|
.setFooter({ text: `${emote} ${reactionCount}` })
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
if (reaction.message.guild.emojis.resolve(emote)) Embed.setFooter({ text: `${reactionCount}`, iconURL: reaction.message.guild.emojis.resolve(emote).url });
|
||||||
|
|
||||||
|
let description = null;
|
||||||
|
|
||||||
|
if (reaction.message.embeds[0]) {
|
||||||
|
if (reaction.message.embeds[0].type == 'image') {
|
||||||
|
messageAttachments = reaction.message.embeds[0].url;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reaction.message.embeds[0].description) {
|
||||||
|
description = reaction.message.embeds[0].description;
|
||||||
|
}
|
||||||
|
else if (reaction.message.content) {
|
||||||
|
description = reaction.message.content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (reaction.message.content) {
|
||||||
|
description = reaction.message.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if message come from nsfw channel and the star/shameboard channel isn't nsfw put it in spoiler
|
||||||
|
if (reaction.message.channel.nsfw && !channel.nsfw) {
|
||||||
|
Embed.setDescription(`||${description}||`);
|
||||||
|
if (messageAttachments != '') {
|
||||||
|
const message = await channel.send({ content: `||${messageAttachments}||`, embeds: [Embed] });
|
||||||
|
global.boards[reaction.message.id] = message.id;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const message = await channel.send({ embeds: [Embed] });
|
||||||
|
global.boards[reaction.message.id] = message.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Embed.setDescription(description);
|
||||||
|
const message = await channel.send({ files: [messageAttachments], embeds: [Embed] })
|
||||||
|
.catch(async () => channel.send({ content: messageAttachments, embeds: [Embed] }));
|
||||||
|
global.boards[reaction.message.id] = message.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
119
events/client/messageReactionRemove.js
Normal file
119
events/client/messageReactionRemove.js
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
import { EmbedBuilder } from 'discord.js';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import db from '../../models/index.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'messageReactionRemove',
|
||||||
|
async execute(reaction, users, c) {
|
||||||
|
if (reaction.partial) {
|
||||||
|
await reaction.fetch()
|
||||||
|
.catch(err => {
|
||||||
|
return console.error(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* I don't really know why this is causing issues.
|
||||||
|
if (reaction.message.partial) {
|
||||||
|
await reaction.message.fetch()
|
||||||
|
.catch(err => {
|
||||||
|
return console.error(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
const isOptOut = await db.optout.findOne({ where: { userID: reaction.message.author.id } });
|
||||||
|
if (isOptOut) return;
|
||||||
|
|
||||||
|
let starboardChannel, shameboardChannel;
|
||||||
|
let reactionCount = reaction.count;
|
||||||
|
|
||||||
|
// If one of the reaction is the author of the message remove 1 to the reaction count
|
||||||
|
reaction.users.cache.forEach(user => {
|
||||||
|
if (reaction.message.author == user) reactionCount--;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Starboard
|
||||||
|
if (fs.existsSync(`./json/board/star${reaction.message.guild.id}.json`)) {
|
||||||
|
starboardChannel = JSON.parse(fs.readFileSync(`./json/board/star${reaction.message.guild.id}.json`));
|
||||||
|
let staremote = starboardChannel.emote;
|
||||||
|
const starcount = starboardChannel.count;
|
||||||
|
// Get name of the custom emoji
|
||||||
|
if (reaction.message.guild.emojis.resolve(staremote.replace(/\D/g, ''))) {
|
||||||
|
staremote = reaction.message.guild.emojis.resolve(staremote.replace(/\D/g, ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (global.boards[reaction.message.id] && (reaction.emoji == staremote || reaction.emoji.name == staremote) && reactionCount < starcount) {
|
||||||
|
const channel = c.channels.resolve(starboardChannel.starboard);
|
||||||
|
const message = await channel.messages.resolve(global.boards[reaction.message.id]);
|
||||||
|
delete global.boards[reaction.message.id];
|
||||||
|
// If it didn't find any message don't do anything
|
||||||
|
if (!message) return;
|
||||||
|
|
||||||
|
message.delete();
|
||||||
|
}
|
||||||
|
else if ((reaction.emoji == staremote || reaction.emoji.name == staremote) && reactionCount >= starcount) {
|
||||||
|
return editEmbed('starboard', staremote, global.boards[reaction.message.id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shameboard
|
||||||
|
if (fs.existsSync(`./json/board/shame${reaction.message.guild.id}.json`)) {
|
||||||
|
shameboardChannel = JSON.parse(fs.readFileSync(`./json/board/shame${reaction.message.guild.id}.json`));
|
||||||
|
let shameemote = shameboardChannel.emote;
|
||||||
|
const shamecount = shameboardChannel.count;
|
||||||
|
// Get name of the custom emoji
|
||||||
|
if (reaction.message.guild.emojis.resolve(shameemote.replace(/\D/g, ''))) {
|
||||||
|
shameemote = reaction.message.guild.emojis.resolve(shameemote.replace(/\D/g, ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (global.boards[reaction.message.id] && (reaction.emoji == shameemote || reaction.emoji.name == shameemote) && reactionCount < shamecount) {
|
||||||
|
const channel = c.channels.resolve(starboardChannel.starboard);
|
||||||
|
const message = await channel.messages.resolve(global.boards[reaction.message.id]);
|
||||||
|
delete global.boards[reaction.message.id];
|
||||||
|
// If it didn't find any message don't do anything
|
||||||
|
if (!message) return;
|
||||||
|
|
||||||
|
message.delete();
|
||||||
|
}
|
||||||
|
else if ((reaction.emoji == shameemote || reaction.emoji.name == shameemote) && reactionCount >= shamecount) {
|
||||||
|
return editEmbed('shameboard', shameemote, global.boards[reaction.message.id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function editEmbed(name, emote, boardID) {
|
||||||
|
let channel;
|
||||||
|
if (name == 'starboard') {
|
||||||
|
channel = c.channels.resolve(starboardChannel.starboard);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
channel = c.channels.resolve(shameboardChannel.shameboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = await channel.messages.resolve(boardID);
|
||||||
|
|
||||||
|
// If the message doesn't have embeds assume it got deleted so don't do anything
|
||||||
|
if (!message) return;
|
||||||
|
|
||||||
|
// If the original embed description is empty make this embed null ( and not empty )
|
||||||
|
let description = message.embeds[0].description;
|
||||||
|
if (!message.embeds[0].description || message.embeds[0].description == undefined) {
|
||||||
|
description = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Embed = new EmbedBuilder()
|
||||||
|
.setColor(reaction.message.member ? reaction.message.member.displayHexColor : 'Navy')
|
||||||
|
.setAuthor({ name: reaction.message.author.username, iconURL: reaction.message.author.displayAvatarURL() })
|
||||||
|
.addFields(
|
||||||
|
{ name: 'Jump to', value: `[message](https://discordapp.com/channels/${reaction.message.guild.id}/${reaction.message.channel.id}/${reaction.message.id})`, inline: true },
|
||||||
|
{ name: 'Channel', value: reaction.message.channel.toString(), inline: true },
|
||||||
|
)
|
||||||
|
.setDescription(description)
|
||||||
|
.setFooter({ text: `${emote} ${reactionCount}` })
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
if (reaction.message.guild.emojis.resolve(emote)) Embed.setFooter({ text: `${reactionCount}`, iconURL: reaction.message.guild.emojis.resolve(emote).url });
|
||||||
|
|
||||||
|
message.edit({ embeds: [Embed] });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
|
@ -1,14 +1,29 @@
|
||||||
import { exec } from 'node:child_process';
|
import { execFile } from 'node:child_process';
|
||||||
import dotenv from 'dotenv';
|
|
||||||
dotenv.config();
|
|
||||||
const { statusChannel, NODE_ENV } = process.env;
|
const { statusChannel, NODE_ENV } = process.env;
|
||||||
|
import { version } from 'discord.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ready',
|
name: 'ready',
|
||||||
once: true,
|
once: true,
|
||||||
async execute(client) {
|
async execute(client) {
|
||||||
|
// Init global variables.
|
||||||
|
global.boards = {};
|
||||||
|
|
||||||
|
const commandSize = client.commands.size;
|
||||||
|
const clientTag = client.user.username;
|
||||||
|
const guildSize = client.guilds.cache.size;
|
||||||
|
const channelSize = client.channels.cache.size;
|
||||||
|
const clientID = client.user.id;
|
||||||
|
|
||||||
|
console.log('===========[ READY ]===========');
|
||||||
|
console.log(`\x1b[32mLogged in as \x1b[34m${clientTag}\x1b[0m! (\x1b[33m${clientID}\x1b[0m)`);
|
||||||
|
console.log(`Ready to serve in \x1b[33m${channelSize}\x1b[0m channels on \x1b[33m${guildSize}\x1b[0m servers.`);
|
||||||
|
console.log(`${client.readyAt}`);
|
||||||
|
console.log(`There is \x1b[33m${commandSize}\x1b[0m command loaded.`);
|
||||||
|
console.log(`Running Discord.js \x1b[33m${version}\x1b[0m`);
|
||||||
|
|
||||||
const ytdlpVersion = await new Promise((resolve, reject) => {
|
const ytdlpVersion = await new Promise((resolve, reject) => {
|
||||||
exec('./bin/yt-dlp --version', (err, stdout, stderr) => {
|
execFile('./bin/yt-dlp', ['--version'], (err, stdout, stderr) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(stderr);
|
reject(stderr);
|
||||||
}
|
}
|
||||||
|
@ -19,24 +34,17 @@ export default {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const commandSize = client.commands.size;
|
|
||||||
const clientTag = client.user.tag;
|
|
||||||
const guildSize = client.guilds.cache.size;
|
|
||||||
const channelSize = client.channels.cache.size;
|
|
||||||
const clientID = client.user.id;
|
|
||||||
|
|
||||||
console.log('===========[ READY ]===========');
|
|
||||||
console.log(`\x1b[32mLogged in as \x1b[34m${clientTag}\x1b[0m! (\x1b[33m${clientID}\x1b[0m)`);
|
|
||||||
console.log(`Ready to serve in \x1b[33m${channelSize}\x1b[0m channels on \x1b[33m${guildSize}\x1b[0m servers.`);
|
|
||||||
console.log(`${client.readyAt}`);
|
|
||||||
console.log(`There is \x1b[33m${commandSize}\x1b[0m command loaded.`);
|
|
||||||
console.log(`Running yt-dlp \x1b[33m${ytdlpVersion.replace('\n', '')}\x1b[0m`);
|
console.log(`Running yt-dlp \x1b[33m${ytdlpVersion.replace('\n', '')}\x1b[0m`);
|
||||||
console.log('===========[ READY ]===========');
|
console.log('===========[ READY ]===========');
|
||||||
|
|
||||||
// If stats channel settings exist, send bot stats to it
|
// If stats channel settings exist, send bot stats to it
|
||||||
if (statusChannel && NODE_ENV !== 'development') {
|
if (statusChannel && NODE_ENV !== 'development') {
|
||||||
const channel = client.channels.resolve(statusChannel);
|
const channel = client.channels.resolve(statusChannel);
|
||||||
channel.send(`Ready to serve in ${channelSize} channels on ${guildSize} servers.\nThere is ${commandSize} command loaded.\nRunning yt-dlp ${ytdlpVersion.replace('\n', '')}\n${client.readyAt}`);
|
channel.send(
|
||||||
|
`Ready to serve in ${channelSize} channels on ${guildSize} servers.\n` +
|
||||||
|
`There is ${commandSize} command loaded.\n` +
|
||||||
|
`Running yt-dlp ${ytdlpVersion.replace('\n', '')}\n` +
|
||||||
|
`${client.readyAt}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
|
@ -1,38 +1,51 @@
|
||||||
import game from '../../json/playing.json' assert {type: 'json'};
|
import { ActivityType } from 'discord.js';
|
||||||
import watch from '../../json/watching.json' assert {type: 'json'};
|
import game from '../../json/playing.json' with {type: 'json'};
|
||||||
|
import music from '../../json/listening.json' with {type: 'json'};
|
||||||
|
import watch from '../../json/watching.json' with {type: 'json'};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ready',
|
name: 'ready',
|
||||||
once: true,
|
once: true,
|
||||||
async execute(client) {
|
async execute(client) {
|
||||||
// Bot status
|
// Bot status
|
||||||
setStatus();
|
await setStatus();
|
||||||
// Change status every 30 minutes
|
// Change status every 30 minutes
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
setStatus();
|
await setStatus();
|
||||||
}, 1800000);
|
}, 1800000);
|
||||||
|
|
||||||
async function setStatus() {
|
async function setStatus() {
|
||||||
const random = Math.floor((Math.random() * 2));
|
const random = Math.floor((Math.random() * 3));
|
||||||
|
let types, status;
|
||||||
// Random "Watching" status taken from json
|
// Random "Watching" status taken from json
|
||||||
if (random === 0) {
|
if (random === 0) {
|
||||||
console.log('Status type: \x1b[32mWatching\x1b[0m');
|
console.log('Status type: \x1b[32mWatching\x1b[0m');
|
||||||
|
|
||||||
let status = watch[Math.floor((Math.random() * watch.length))];
|
status = watch[Math.floor((Math.random() * watch.length))];
|
||||||
status = status + ' | Now with slash commands!';
|
status = status + ' | Now with slash commands!';
|
||||||
console.log(`Setting status to: ${status}`);
|
console.log(`Setting status to: ${status}`);
|
||||||
client.user.setActivity(status, { type: 'WATCHING' });
|
types = [ ActivityType.Watching ];
|
||||||
}
|
}
|
||||||
// Random "Playing" status taken from json
|
// Random "Playing" status taken from json
|
||||||
else if (random === 1) {
|
else if (random === 1) {
|
||||||
console.log('Status type: \x1b[32mPlaying\x1b[0m');
|
console.log('Status type: \x1b[32mPlaying\x1b[0m');
|
||||||
|
|
||||||
let status = game[Math.floor((Math.random() * game.length))];
|
status = game[Math.floor((Math.random() * game.length))];
|
||||||
status = status + ' | Now with slash commands!';
|
status = status + ' | Now with slash commands!';
|
||||||
|
|
||||||
console.log(`Setting status to: ${status}`);
|
console.log(`Setting status to: ${status}`);
|
||||||
client.user.setActivity(status, { type: 'PLAYING' });
|
types = [ ActivityType.Playing, ActivityType.Competing ];
|
||||||
}
|
}
|
||||||
|
else if (random === 2) {
|
||||||
|
console.log('Status type: \x1b[32mPlaying\x1b[0m');
|
||||||
|
|
||||||
|
status = music[Math.floor((Math.random() * music.length))];
|
||||||
|
status = status + ' | Now with slash commands!';
|
||||||
|
|
||||||
|
console.log(`Setting status to: ${status}`);
|
||||||
|
types = [ ActivityType.Listening ];
|
||||||
|
}
|
||||||
|
await client.user.setActivity(status, { type: types[Math.floor((Math.random() * types.length))] });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
|
@ -1,6 +1,4 @@
|
||||||
import https from 'node:https';
|
import https from 'node:https';
|
||||||
import dotenv from 'dotenv';
|
|
||||||
dotenv.config();
|
|
||||||
const { uptimeURL, uptimeInterval } = process.env;
|
const { uptimeURL, uptimeInterval } = process.env;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
65
index.js
65
index.js
|
@ -1,26 +1,35 @@
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||||
import { Client, Collection, Intents } from 'discord.js';
|
import { Client, Collection, GatewayIntentBits, Partials } from 'discord.js';
|
||||||
import dotenv from 'dotenv';
|
const { token, NODE_ENV } = process.env;
|
||||||
dotenv.config();
|
|
||||||
const { token } = process.env;
|
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
const client = new Client({ intents: [Intents.FLAGS.GUILDS], shards: 'auto' });
|
const client = new Client({
|
||||||
|
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, GatewayIntentBits.GuildMessageReactions, GatewayIntentBits.GuildMembers],
|
||||||
|
partials: [Partials.Message, Partials.Reaction, Partials.Channel],
|
||||||
|
shards: 'auto',
|
||||||
|
});
|
||||||
|
|
||||||
// Load commands
|
// Load commands
|
||||||
client.commands = new Collection();
|
client.commands = new Collection();
|
||||||
await loadCommandFromDir('fun');
|
|
||||||
await loadCommandFromDir('utility');
|
fs.readdir(`${__dirname}/commands`, (err, categoryPath) => {
|
||||||
await loadCommandFromDir('admin');
|
if (err) {
|
||||||
await loadCommandFromDir('owner');
|
return console.error(err);
|
||||||
|
}
|
||||||
|
categoryPath.forEach(category => {
|
||||||
|
loadCommandFromDir(category);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Load events
|
// Load events
|
||||||
await loadEventFromDir('client', client);
|
loadEventFromDir('client', client);
|
||||||
await loadEventFromDir('process', process);
|
if (NODE_ENV !== 'development') {
|
||||||
|
loadEventFromDir('process', process);
|
||||||
|
}
|
||||||
|
|
||||||
client.login(token);
|
client.login(token);
|
||||||
|
|
||||||
|
@ -30,10 +39,13 @@ async function loadCommandFromDir(dir) {
|
||||||
|
|
||||||
for (const file of commandFiles) {
|
for (const file of commandFiles) {
|
||||||
const filePath = path.join(commandsPath, file);
|
const filePath = path.join(commandsPath, file);
|
||||||
let command = await import(filePath);
|
import(pathToFileURL(filePath))
|
||||||
command = command.default;
|
.then(importedCommand => {
|
||||||
|
const command = importedCommand.default;
|
||||||
client.commands.set(command.data.name, command);
|
client.commands.set(command.data.name, command);
|
||||||
|
console.log(`Successfully loaded command \x1b[32m${command.category}/${command.data.name}\x1b[0m`);
|
||||||
|
})
|
||||||
|
.catch(error => console.error(`Failed to load command for path: ${filePath}`, error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,13 +55,16 @@ async function loadEventFromDir(dir, listener) {
|
||||||
|
|
||||||
for (const file of eventFiles) {
|
for (const file of eventFiles) {
|
||||||
const filePath = path.join(eventsPath, file);
|
const filePath = path.join(eventsPath, file);
|
||||||
let event = await import(filePath);
|
import(pathToFileURL(filePath))
|
||||||
event = event.default;
|
.then(importedEvent => {
|
||||||
if (event.once) {
|
const event = importedEvent.default;
|
||||||
listener.once(event.name, (...args) => event.execute(...args, client));
|
if (event.once) {
|
||||||
}
|
listener.once(event.name, (...args) => event.execute(...args, client));
|
||||||
else {
|
}
|
||||||
listener.on(event.name, (...args) => event.execute(...args, client));
|
else {
|
||||||
}
|
listener.on(event.name, (...args) => event.execute(...args, client));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => console.error(`Failed to load event for path: ${filePath}`, error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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."]
|
1
json/dictionary/activities.json
Normal file
1
json/dictionary/activities.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
["baseball", "basketball", "football", "golf", "soccer", "tennis"]
|
1
json/dictionary/adjectives.json
Normal file
1
json/dictionary/adjectives.json
Normal file
File diff suppressed because one or more lines are too long
1
json/dictionary/adverbs.json
Normal file
1
json/dictionary/adverbs.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
["10 years later", "a day ago", "a day later", "a month ago", "a month later", "a week ago", "a week later", "a year ago", "a year later", "absentmindedly", "accidentally", "actively", "adventurously", "again", "all the time", "an hour ago", "an hour later", "anally", "angrily", "animatedly", "anxiously", "appreciatively", "ardently", "arrogantly", "artfully", "at sunrise", "at sunset", "at the full moon", "awkwardly", "barely", "beautifully", "billions of years ago", "bitterly", "biweekly", "blissfully", "boldly", "bravely", "brightly", "briskly", "buoyantly", "busily", "calmly", "carefully", "carelessly", "casually", "cautiously", "centenially", "certainly", "closely", "coaxingly", "coldly", "comfortably", "considerably", "continually", "cooly", "correctly", "courageously", "crossly", "crunchily", "curiously", "daily", "daintily", "deeply", "defiantly", "deliberately", "delicately", "deliciously", "delightedly", "densely", "determinedly", "diligently", "discreetly", "dreamily", "eagerly", "earnestly", "efficiently", "effortlessly", "elegantly", "energetically", "enthusiastically", "enviously", "every Tuesday", "every night", "every now and then", "evilly", "excessively", "excitedly", "expertly", "explosively", "extremely", "faithfully", "fearlessly", "ferociously", "fervently", "fiercely", "firmly", "five minutes later", "five years later", "fleetingly", "fluidly", "for 10 weeks", "for 36 hours", "forcibly", "forgivingly", "formally", "frantically", "frequently", "from now on", "generously", "genuinely", "gladly", "gleefully", "gloriously", "grandly", "gravely", "greatly", "greedily", "grudgingly", "grumpily", "happily", "hardly", "harmonically", "hastily", "hatefully", "heartily", "heavily", "hollowly", "honestly", "hungrily", "hurriedly", "illegally", "immediately", "imperfectly", "inappropriately", "indubitably", "instantly", "intensely", "intensively", "intentionally", "intently", "intuitively", "joyfully", "just 5 minutes ago", "justly", "keenly", "kindly", "laboriously", "later", "lightly", "loudly", "lovingly", "magestically", "magically", "majestically", "meekly", "mercifully", "merrily", "methodically", "mildly", "millions of years ago", "modestly", "mortally", "musically", "mysteriously", "naturally", "neatly", "necessarily", "never", "never again", "nimbly", "noisily", "nonchalantly", "now", "objectively", "obscenely", "occasionally", "often", "on Mondays", "once a month", "once a week", "once again", "once in a while", "outstandingly", "partially", "patiently", "peacefully", "perfectly", "perpetually", "persistently", "pointedly", "presently", "professionally", "profusely", "promptly", "proudly", "purposefully", "quickly", "quietly", "quite", "rapidly", "rashly", "readily", "really", "rebelliously", "recently", "recklessly", "regretfully", "repeatedly", "restlessly", "richly", "romantically", "roughly", "royally", "sadly", "saggingly", "sarcastically", "secretly", "several times", "shamefully", "sharply", "side to side", "silently", "single-handedly", "six months later", "slickly", "slimily", "sloppily", "slowly", "sluggishly", "smartly", "smoothly", "snugly", "so", "solemnly", "sometimes", "speedily", "spiritedly", "spontaneously", "stealthily", "strenuously", "strictly", "strongly", "stubbornly", "suspiciously", "sweetly", "sympathetically", "systematically", "terribly", "terrifyingly", "thickly", "thirstily", "this time", "thoroughly", "thoughtlessly", "ticklishly", "to kingdom come", "today", "tonight", "too", "torturously", "trillions of years ago", "truly", "unbelievably", "unnecessarily", "until further notice", "up and down", "urgently", "usually", "very", "victoriously", "vigilantly", "vigorously", "violently", "warily", "weakly", "wetly", "wholeheartedly", "wildly", "wisely", "with haste", "yesterday"]
|
1
json/dictionary/celebrities.json
Normal file
1
json/dictionary/celebrities.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
["50 Cent", "Adam Sandler", "Alden Ehrenreich", "Anne Hathaway", "Arnold Schwarzenegger", "Axl Rose"," Ben Shapiro", "Ben Stein", "Ben Stiller", "Bernie Sanders", "Bill Murray", "Brad Pitt", "Channing Tatum", "Christian Bale", "Daniel Radcliffe", "Danny Masterson", "David Lee Roth", "Donald Trump", "Duff McKagan", "Eddie Van Halen", "Edward Norton", "Eminem", "Emma Stone", "Emma Watson", "Geoffery Lewis", "Hillary Clinton", "Hugh Jackson", "Jack Black", "Jake Epstein", "Jesse Eisenberg", "Jessica Alba", "John Cena", "John Travolta", "Johnny Depp", "Julia Garner", "Julia Roberts", "Justin Bieber", "Laura Prepon", "Lena Dunham", "Leonardo DiCaprio", "Marilyn Manson", "Megan Fox", "Michael Cera", "Morgan Freeman", "Natalie Portman", "Nicolas Cage", "Orlando Bloom", "Rihanna", "Robert Downey Jr.", "Robin Willians", "Ryan Seacrest", "Scarlett Johansson", "Serj Tankian", "Shia LaBeouf", "Slash", "Tom Cruise", "Tom Hanks", "Trent Reznor", "Will Ferrell", "Will Smith", "Woody Allen", "Zac Effron"]
|
1
json/dictionary/countries.json
Normal file
1
json/dictionary/countries.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
["Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra", "Angola", "Anguilla", "Antarctica", "Antigua and Barbuda", "Argentina", "Armenia", "Aruba", "Ashmore and Cartier Islands", "Australia", "Austria", "Azerbaijan", "Bahrain", "Bangladesh", "Barbados", "Bassas da India", "Belarus", "Belgium", "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegovina", "Botswana", "Bouvet Island", "Brazil", "British Indian Ocean Territory", "British Virgin Islands", "Brunei", "Bulgaria", "Burkina Faso", "Burma", "Burundi", "Cambodia", "Cameroon", "Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China", "Christmas Island", "Clipperton Island", "Cocos Islands", "Colombia", "Comoros", "Cook Islands", "Coral Sea Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Czech Republic", "Democratic Republic of the Congo", "Denmark", "Dhekelia", "Djibouti", "Dominican Republic", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Europa Island", "Falkland Islands", "Faroe Islands", "Federated States of Micronesia", "Fiji", "Finland", "France", "French Guiana", "French Polynesia", "French Southern and Antarctic Lands", "Gabon", "Gaza Strip", "Georgia", "Germany", "Ghana", "Gibraltar", "Glorioso Islands", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea", "Guinea_Bissau", "Guyana", "Haiti", "Heard Island and McDonald Islands", "Holy See", "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland", "Isle of Man", "Israel", "Italy", "Jan Mayen", "Japan", "Jersey", "Jordan", "Juan de Nova Island", "Kazakhstan", "Kenya", "Kiribati", "Kuwait", "Kyrgyzstan", "Laos", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg", "Macau", "Macedonia", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique", "Mauritania", "Mauritius", "Mayotte", "Moldova", "Monaco", "Mongolia", "Montserrat", "Morocco", "Mozambique", "Namibia", "Nauru", "Navassa Island", "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger", "Nigeria", "Niue", "Norfolk Island", "North Korea", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau", "Panama", "Papua New Guinea", "Paracel Islands", "Paraguay", "Peru", "Philippines", "Pitcairn Islands", "Poland", "Portugal", "Puerto Rico", "Qatar", "Republic of the Congo", "Reunion", "Romania", "Russia", "Rwanda", "Saint Helena", "Saint Kitts and Nevis", "Saint Lucia", "Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino", "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia and Montenegro", "Seychelles", "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa", "South Georgia and the South Sandwich Islands", "South Korea", "Spain", "Spratly Islands", "Sri Lanka", "Sudan", "Suriname", "Svalbard", "Swaziland", "Sweden", "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "The Bahamas", "The Gambia", "Timor_Leste", "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tromelin Island", "Tunisia", "Turkey", "Turkmenistan", "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela", "Vietnam", "Virgin Islands", "Wake Island", "Wallis and Futuna", "West Bank", "Western Sahara", "Yemen", "Zambia", "Zimbabwe"]
|
1
json/dictionary/diseases.json
Normal file
1
json/dictionary/diseases.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
["AIDS", "Alzheimer's", "Down syndrome", "Hepatitis A", "Hepatitis B", "Hepatitis C", "acute inclusion body encephalitis", "albinism", "alveolitis", "amnesia", "anal sacculitis", "anaphylaxis", "appendicitis", "arsenic poisoning", "arthritis", "athlete's foot", "autism", "bipolar disorder", "bronchitis", "deep vein thrombosis", "delirium", "dementia", "depression", "diarrhea", "dissociative identity disorder", "ebola", "epilepsy", "fibrosis", "folliculitis", "gastroenteritis", "halitosis", "herpes", "hyperthyroidism", "hypothyroidism", "influenza", "laryngitis", "lead poisoning", "lupus", "meningitis", "mental retardation", "mercury poisoning", "osteoporosis", "pinkeye", "plague", "rheumatoid arthritis", "sacculitis", "schizophrenia", "smallpox", "strep throat", "tetanus", "tonsilitis", "type I diabetes", "type II diabetes", "typhoid fever"]
|
1
json/dictionary/elements.json
Normal file
1
json/dictionary/elements.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
["actinium", "aluminum", "americium", "antimony", "argon", "arsenic", "astatine", "barium", "berkelium", "beryllium", "bismusth", "boron", "bromine", "cadmium", "calcium", "californium", "carbon", "cerium", "cesium", "chlorine", "chromium", "cobalt", "copper", "curium", "dysprosium", "einsteinium", "erbium", "europium", "fermium", "fluorine", "francium", "gadolinium", "gallium", "germanium", "gold", "hafnium", "helium", "holmium", "hydrogen", "indium", "iodine", "iridium", "iron", "krypton", "lanthanum", "lawrencium", "lead", "lithium", "lutetium", "magnesium", "manganese", "mendelevium", "mercury", "molybdenum", "neodymium", "neon", "neptunium", "nickel", "niobium", "nitrogen", "nobelium", "osmium", "oxygen", "palladium", "phosphorus", "platinum", "plutonium", "polonium", "potassium", "praseodymium", "promethium", "protactinium", "radium", "radon", "rhenium", "rhodium", "rubidium", "ruthenium", "samarium", "scandium", "selenium", "silicon", "silver", "sodium", "strontium", "sulfur", "tantalum", "technetium", "tellurium", "terbium", "thallium", "thorium", "thulium", "tin", "titanium", "tungsten", "uranium", "vanadium", "xenon", "ytterbium", "yttrium", "zinc", "zircomium"]
|
1
json/dictionary/exclamations.json
Normal file
1
json/dictionary/exclamations.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
["as if!", "no way!", "boo!", "please!", "oh my!", "that can't be!", "wow!", "oh boy...", "oh boy!", "oh dear", "ew!", "gross!", "ugh!", "big deal!", "whatever!", "I don't care!", "big whoop!", "it doesn't matter!", "get out!", "duh!", "no kidding!", "well, duh!", "well, yeah!", "you don't say?", "oh, no!", "finally!", "at last!", "at long last!", "about time!", "it's about time!", "yuck!", "yucky!", "ick!", "icky!", "rats!", "Jesus Christ!", "mother of god!", "amazing!", "damn it!"]
|
1
json/dictionary/greetings.json
Normal file
1
json/dictionary/greetings.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
["hello", "hi", "hey", "greetings", "good day", "good morning", "good afternoon", "good evening"]
|
1
json/dictionary/hobbies.json
Normal file
1
json/dictionary/hobbies.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
["PC building", "acting", "antique-shopping", "baking", "bank robbery", "baseball", "basket-weaving", "calligraphy", "camping", "cannibalism", "carving", "cooking", "cooking meth", "crocheting", "dancing", "deep-sea diving", "drawing", "drug dealing", "fantasy football", "farming", "filmmaking", "fishing", "flying", "gaming", "gardening", "glassblowing", "golf", "guitar", "hiking", "human dissection", "hunting", "knitting", "money laundering", "mushroom cultivation", "music composition", "opera singing", "painting", "partying", "piano", "poetry", "politics", "pottery", "programming", "pyrotechnics", "resurrecting the dead", "running", "sailing", "scuba diving", "sculpting", "sewing", "shopping", "singing", "skateboarding", "skydiving", "spearfishing", "taxidermy", "theater", "time travel", "toy collecting", "urban exploration", "web-browsing", "whittling", "writing"]
|
1
json/dictionary/music.json
Normal file
1
json/dictionary/music.json
Normal file
File diff suppressed because one or more lines are too long
1
json/dictionary/noun.json
Normal file
1
json/dictionary/noun.json
Normal file
File diff suppressed because one or more lines are too long
1
json/dictionary/prefixes.json
Normal file
1
json/dictionary/prefixes.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
["a", "anti", "auto", "bi", "circum", "contra", "endo", "exo", "extra", "fore", "homo", "hyper", "in", "intra", "mega", "mid", "mini", "mis", "mono", "non", "octo", "omni", "over", "penta", "post", "pre", "pseudo", "psycho", "pyro", "quad", "semi", "sub", "super", "trans", "tri", "un", "under", "uni"]
|
1
json/dictionary/pronouns.json
Normal file
1
json/dictionary/pronouns.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
["he", "it", "she", "they"]
|
1
json/dictionary/states.json
Normal file
1
json/dictionary/states.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
["Alabama", "Alaska", "Arizona", "Arkansas", "Baden-W\u00fcrttemberg", "Bavaria", "Berlin", "Brandenburg", "Bremen", "California", "Colorado", "Connecticut", "Delaware", "Florida", "Georgia", "Hamburg", "Hawaii", "Hesse", "Idaho", "Illinois", "Indiana", "Iowa", "Kansas", "Kentucky", "Louisiana", "Lower Saxony", "Maine", "Maryland", "Massachusetts", "Mecklenburg-Vorpommern", "Michigan", "Minnesota", "Mississippi", "Missouri", "Montana", "Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico", "New York", "North Carolina", "North Dakota", "North Rhine-Westphalia", "Ohio", "Oklahoma", "Oregon", "Pennsylvania", "Rhineland-Palatinate", "Rhode Island", "Saarland", "Saxony", "Saxony-Anhalt", "Schleswig-Holstein", "South Carolina", "South Dakota", "Tennessee", "Texas", "Thuringia", "Utah", "Vermont", "Virginia", "Washington", "West Virginia", "Wisconsin", "Wyoming"]
|
1
json/dictionary/titles.json
Normal file
1
json/dictionary/titles.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
["Colonel", "Daddy", "Dojo", "Dr.", "Governor", "Granny", "Honorable", "King", "Madam", "Mama", "Master", "Mayor", "Mistress", "Moist", "Mr.", "Mrs.", "Ms", "Old", "Papa", "Prince", "Professor", "Queen", "Sensei", "Sergeant", "Sir"]
|
1
json/dictionary/units.json
Normal file
1
json/dictionary/units.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
["bucket", "centimeter", "century", "cubic centimeter", "cup", "day", "foot", "gallon", "gram", "handful", "hour", "inch", "joule", "kilogram", "kilojoule", "kilometer", "kilovolt", "kilowatt", "light-year", "liter", "megaton", "megawatt", "meter", "microfarad", "mile", "milliampere", "milliliter", "millisecond", "millivolt", "milliwatt", "minute", "month", "mouthful", "ounce", "pint", "pound", "quart", "second", "tablespoon", "teaspoon", "ton", "yard", "year"]
|
1
json/dictionary/verbs.json
Normal file
1
json/dictionary/verbs.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
["abduct", "abolish", "align", "amend", "amputate", "annihilate", "announce", "appreciate", "apprehend", "articulate", "assault", "associate", "attack", "authenticate", "bake", "bang", "barbeque", "bathe", "bawl", "beep", "behead", "belly-flop", "bind", "bite", "blacken", "blast", "bleed", "bless", "bloat", "bloom", "blossom", "blow", "bludgeon", "boil", "breastfeed", "breathe", "bubble", "burn", "burp", "cackle", "call", "caress", "chew", "chill", "choke", "chomp", "chop", "chow", "churn", "clean", "click", "clip", "commandeer", "conserve", "convict", "cook", "crack", "cram", "crank", "crash", "crawl", "cremate", "cripple", "croak", "crouch", "crumple", "crunch", "crush", "cry", "cuddle", "cultivate", "customize", "cut", "cut in half", "dangle", "darken", "decapitate", "declare", "decorate", "deep-fry", "defecate", "defy", "deny", "despise", "dice", "dig", "discipline", "dishonor", "dislike", "dismantle", "dissect", "dominate", "donate", "draft", "drain", "dramatize", "drip", "dry-freeze", "dye", "eat", "ejaculate", "eject", "elect", "electrocute", "eliminate", "embrace", "enforce", "examine", "exclaim", "explode", "exploit", "extend", "extrapolate", "extrude", "fabricate", "fall apart", "falsify", "fart", "feed", "fester", "fiddle", "fight", "fistfight", "fizz", "flap", "flash", "flatten", "flick", "floss", "flutter", "fly", "force", "forecast", "freeze", "froth", "fume", "gallop", "gargle", "gleam", "glorify", "glow", "glue", "gnaw", "gouge", "grab", "grate", "grind", "grip", "groom", "grow", "grunt", "gulp", "guzzle", "gyrate", "hack", "hammer", "handle", "hang", "harass", "harden", "headbutt", "hiccup", "hiss", "hog-tie", "hoist", "hoot", "hug", "hunt", "hurl", "hypnotize", "impale", "impeach", "impede", "implode", "inaugurate", "infest", "ingest", "inject", "injure", "invigorate", "iron", "jargogle", "jerk", "jet-spray", "jimmy", "jingle", "joust", "kick", "kidnap", "kill", "kiss", "knead", "knock out", "lather", "laugh", "lay", "lecture", "legalize", "lick", "lighten", "like", "liquefy", "liquidate", "loathe", "loosen", "maim", "make fun of", "mangle", "manhandle", "manipulate", "march", "marinate", "massage", "masticate", "maul", "mist", "misuse", "moan", "moisten", "move", "mumble", "mutilate", "mutter", "nab", "nail", "need", "nip", "obfuscate", "oil", "organize", "paint", "palpitate", "penetrate", "pepper", "petition", "pickle", "pierce", "pillage", "pinch", "piss", "plaster", "pluck", "plunge", "poke", "polish", "pop", "pour", "pray", "preen", "press", "probe", "prod", "prosecute", "prove", "puff", "pull", "pump", "punch", "punish", "purify", "push", "put up with", "quaff", "quantify", "radiate", "ram", "rapture", "ratify", "rattle", "rearrange", "recycle", "report", "ride", "rip", "ripple", "rise", "roar", "rob", "roll", "rot", "rub", "run", "run over", "run after", "rustle", "salt", "sanitize", "savor", "say", "scold", "scorch", "scratch", "scream", "screech", "screw", "scrub", "scrunch", "sculpt", "season", "serve", "set on fire", "shake", "sharpen", "shatter", "shave", "shimmer", "shine", "shiver", "shoot", "shout", "shove", "shower", "shred", "shriek", "shrink", "shudder", "sit", "skewer", "skip", "slam", "slap", "slash", "slay", "sleepwalk", "slip", "slit", "slouch", "slurp", "smack", "smear", "smoke", "smolder", "snap", "snicker", "sniff", "snip", "snoop", "snort", "snuffle", "snuggle", "soak", "soften", "solidify", "spank", "sparkle", "spelunk", "spit", "splash", "splatter", "spray", "sprinkle", "sprint", "spurt", "sputter", "square dance", "squat", "squeal", "squeeze", "squelch", "squirt", "squish", "stab", "stampede", "stand", "staple", "steam", "stew", "stick", "stimulate", "stir", "stomp", "strain", "strangle", "strap", "strategize", "streamline", "stress-test", "strike", "strut", "stuff", "suckle", "sue", "superglue", "swallow", "swear", "swipe", "tap", "tape", "throttle", "throw", "tickle", "tighten", "tinkle", "tiptoe", "toast", "toke", "tongue", "touch", "transcribe", "tremble", "trot", "tune", "twang", "twinkle", "twist", "undermine", "undress", "undulate", "uproot", "urinate", "vaporize", "venerate", "verify", "veto", "vibrate", "vomit", "waddle", "wail", "walk", "want", "waste", "wave", "wedge", "whimper", "whine", "whip", "whisper", "whiten", "wiggle", "wilt", "withdraw", "wrinkle", "yank", "yell", "zip"]
|
1
json/dictionary/yesno.json
Normal file
1
json/dictionary/yesno.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
["yes", "yeah", "sure", "indeed", "affirmative", "absolutely", "yup", "yep", "no", "nope", "nah", "negative", "absolutely not"]
|
3
json/listening.json
Normal file
3
json/listening.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[
|
||||||
|
"psychometricBussdown by oddballTheatre"
|
||||||
|
]
|
27
migrations/20220829184747-create-optout.js
Normal file
27
migrations/20220829184747-create-optout.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
'use strict';
|
||||||
|
module.exports = {
|
||||||
|
async up(queryInterface, Sequelize) {
|
||||||
|
await queryInterface.createTable('optouts', {
|
||||||
|
id: {
|
||||||
|
allowNull: false,
|
||||||
|
autoIncrement: true,
|
||||||
|
primaryKey: true,
|
||||||
|
type: Sequelize.INTEGER
|
||||||
|
},
|
||||||
|
userID: {
|
||||||
|
type: Sequelize.BIGINT
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
allowNull: false,
|
||||||
|
type: Sequelize.DATE
|
||||||
|
},
|
||||||
|
updatedAt: {
|
||||||
|
allowNull: false,
|
||||||
|
type: Sequelize.DATE
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async down(queryInterface, Sequelize) {
|
||||||
|
await queryInterface.dropTable('optouts');
|
||||||
|
}
|
||||||
|
};
|
23
models/optout.js
Normal file
23
models/optout.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
'use strict';
|
||||||
|
const {
|
||||||
|
Model
|
||||||
|
} = require('sequelize');
|
||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
class optout extends Model {
|
||||||
|
/**
|
||||||
|
* Helper method for defining associations.
|
||||||
|
* This method is not a part of Sequelize lifecycle.
|
||||||
|
* The `models/index` file will call this method automatically.
|
||||||
|
*/
|
||||||
|
static associate(models) {
|
||||||
|
// define association here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
optout.init({
|
||||||
|
userID: DataTypes.BIGINT
|
||||||
|
}, {
|
||||||
|
sequelize,
|
||||||
|
modelName: 'optout',
|
||||||
|
});
|
||||||
|
return optout;
|
||||||
|
};
|
6352
package-lock.json
generated
6352
package-lock.json
generated
File diff suppressed because it is too large
Load diff
34
package.json
34
package.json
|
@ -4,9 +4,9 @@
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node .",
|
"start": "node --env-file .env .",
|
||||||
"deploy": "node deploy-commands.cjs",
|
"deploy": "node --env-file .env scripts/deploy-commands.js",
|
||||||
"deployGlobally": "node deploy-commands.cjs global",
|
"deployGlobally": "node --env-file .env scripts/deploy-commands.js global",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"lintfix": "eslint . --fix",
|
"lintfix": "eslint . --fix",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
@ -17,23 +17,25 @@
|
||||||
"homepage": "https://libtar.de",
|
"homepage": "https://libtar.de",
|
||||||
"license": "AGPL",
|
"license": "AGPL",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordjs/builders": "^0.13.0",
|
"@discordjs/rest": "^2.3.0",
|
||||||
"@discordjs/rest": "^0.4.1",
|
"discord-api-types": "^0.37.91",
|
||||||
"discord-api-types": "^0.33.1",
|
"discord.js": "^14.15.3",
|
||||||
"discord.js": "^13.7.0",
|
"mariadb": "^3.3.1",
|
||||||
"dotenv": "^16.0.1",
|
"node-fetch": "^3.3.2",
|
||||||
"mariadb": "^3.0.1",
|
"sequelize": "^6.37.3",
|
||||||
"mysql2": "^2.3.3",
|
"turndown": "^7.2.0",
|
||||||
"node-fetch": "^3.2.6",
|
"twitter-api-v2": "^1.17.1",
|
||||||
"sequelize": "^6.21.3",
|
"ytpplus-node": "github:Supositware/ytpplus-node"
|
||||||
"turndown": "^7.1.1",
|
|
||||||
"twit": "^2.2.11"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/eslint-parser": "^7.18.9",
|
"@babel/eslint-parser": "^7.18.9",
|
||||||
"@babel/plugin-syntax-import-assertions": "^7.18.6",
|
"@babel/plugin-syntax-import-assertions": "^7.18.6",
|
||||||
|
"@eslint/eslintrc": "^3.1.0",
|
||||||
|
"@eslint/js": "^9.6.0",
|
||||||
"@types/node": "^18.7.3",
|
"@types/node": "^18.7.3",
|
||||||
"eslint": "^8.16.0",
|
"eslint": "^8.57.0",
|
||||||
"sequelize-cli": "^6.4.1"
|
"globals": "^15.8.0",
|
||||||
|
"sequelize-cli": "^6.4.1",
|
||||||
|
"sqlite3": "^5.1.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
29
prereq.cjs
29
prereq.cjs
|
@ -1,29 +0,0 @@
|
||||||
const fs = require('node:fs');
|
|
||||||
const https = require('node:https');
|
|
||||||
|
|
||||||
console.log('Downloading latest version of yt-dlp');
|
|
||||||
|
|
||||||
const downloadUrl = 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp';
|
|
||||||
download(downloadUrl);
|
|
||||||
|
|
||||||
function download(url) {
|
|
||||||
https.get(url, (res) => {
|
|
||||||
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
||||||
console.log(`yt-dlp download url: ${res.headers.location}`);
|
|
||||||
return download(res.headers.location);
|
|
||||||
}
|
|
||||||
|
|
||||||
const path = './bin/yt-dlp';
|
|
||||||
const filePath = fs.createWriteStream(path);
|
|
||||||
res.pipe(filePath);
|
|
||||||
filePath.on('finish', () => {
|
|
||||||
filePath.close();
|
|
||||||
fs.chmodSync('./bin/yt-dlp', '755');
|
|
||||||
console.log('yt-dlp download finished.');
|
|
||||||
});
|
|
||||||
filePath.on('error', (err) => {
|
|
||||||
filePath.close();
|
|
||||||
console.error(err.message);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
12
readme.md
12
readme.md
|
@ -11,12 +11,12 @@ These instructions will get you a copy of the project up and running on your loc
|
||||||
You need to install the following
|
You need to install the following
|
||||||
|
|
||||||
|
|
||||||
* ffmpeg (Optional but very recommanded: for yt-dlp to merge video/audio formats)
|
* 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](prereq.cjs))
|
* yt-dlp ([a file can download it for you](scripts/updateytdlp.js))
|
||||||
* HandBrakeCLI (For [download](commands/utility/download.js))
|
* HandBrakeCLI (For [download](commands/utility/download.js))
|
||||||
* gifsicle (For [vid2gif](commands/utility/vid2gif.js))
|
* gifsicle (For [vid2gif](commands/utility/vid2gif.js))
|
||||||
* gifki (For [vid2gif](commands/utility/vid2gif.js))
|
* gifki (For [vid2gif](commands/utility/vid2gif.js))
|
||||||
* Somewhere to upload files larger than 8 mb (I use a self hosted [XBackBone](https://github.com/SergiX44/XBackBone/) with the upload.sh script made from it, you can use anything else just need to be located in bin/upload.sh)
|
* Somewhere to upload files larger than the file limit, currently 25 mb. (I use a self hosted [XBackBone](https://github.com/SergiX44/XBackBone/) with the upload.sh script made from it, you can use anything else just need to be located in bin/upload.sh)
|
||||||
|
|
||||||
### Installing
|
### Installing
|
||||||
```
|
```
|
||||||
|
@ -27,10 +27,10 @@ npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
To run the bot for the first time you need to execute [deploy-commands.js](scripts/deploy-commands.js) so the commands can be registered, don't forget to set your .env accordingly.
|
To run the bot for the first time you need to execute [deploy-commands.js](scripts/deploy-commands.js) so the commands can be registered, don't forget to set your .env accordingly.
|
||||||
``node scripts/deploy-commands.cjs``
|
``node --env-file .env scripts/deploy-commands.cjs``
|
||||||
|
|
||||||
then you can just run it normally.
|
then you can just run it normally.
|
||||||
``node index.js``
|
``node --env-file .env index.js``
|
||||||
|
|
||||||
If you want to run the bot automatically you can use pm2
|
If you want to run the bot automatically you can use pm2
|
||||||
```
|
```
|
||||||
|
@ -38,7 +38,7 @@ npm install -g pm2
|
||||||
pm2 start index.js --name (insert name)
|
pm2 start index.js --name (insert name)
|
||||||
```
|
```
|
||||||
If you are on linux and don't need automatic restart on crash you can just do
|
If you are on linux and don't need automatic restart on crash you can just do
|
||||||
``nohup node index.js &``
|
``nohup node --env-file .env index.js &``
|
||||||
|
|
||||||
## Built With
|
## Built With
|
||||||
|
|
||||||
|
|
|
@ -1,134 +0,0 @@
|
||||||
const { SlashCommandBuilder } = require('@discordjs/builders');
|
|
||||||
const { REST } = require('@discordjs/rest');
|
|
||||||
const { Routes } = require('discord-api-types/v9');
|
|
||||||
require('dotenv').config();
|
|
||||||
const { clientId, guildId, token } = process.env;
|
|
||||||
|
|
||||||
const commands = [
|
|
||||||
new SlashCommandBuilder()
|
|
||||||
.setName('ping')
|
|
||||||
.setDescription('Replies with pong!'),
|
|
||||||
|
|
||||||
new SlashCommandBuilder()
|
|
||||||
.setName('download')
|
|
||||||
.setDescription('Download a video.')
|
|
||||||
.addStringOption(option =>
|
|
||||||
option.setName('url')
|
|
||||||
.setDescription('url of the video you want to download.')
|
|
||||||
.setRequired(true))
|
|
||||||
.addBooleanOption(option =>
|
|
||||||
option.setName('format')
|
|
||||||
.setDescription('Choose the quality of the video.')
|
|
||||||
.setRequired(false))
|
|
||||||
.addBooleanOption(option =>
|
|
||||||
option.setName('compress')
|
|
||||||
.setDescription('Compress the video?')
|
|
||||||
.setRequired(false)),
|
|
||||||
|
|
||||||
|
|
||||||
new SlashCommandBuilder()
|
|
||||||
.setName('reddit')
|
|
||||||
.setDescription('Send random images from the subreddit you choose')
|
|
||||||
.addStringOption(option =>
|
|
||||||
option.setName('subreddit')
|
|
||||||
.setDescription('The subreddit you wish to see')
|
|
||||||
.setRequired(true)),
|
|
||||||
|
|
||||||
new SlashCommandBuilder()
|
|
||||||
.setName('vid2gif')
|
|
||||||
.setDescription('Convert your video into a gif.')
|
|
||||||
.addStringOption(option =>
|
|
||||||
option.setName('url')
|
|
||||||
.setDescription('URL of the video you want to convert')
|
|
||||||
.setRequired(true)),
|
|
||||||
|
|
||||||
new SlashCommandBuilder()
|
|
||||||
.setName('feedback')
|
|
||||||
.setDescription('Send a feedback to the developer.')
|
|
||||||
.addStringOption(option =>
|
|
||||||
option.setName('feedback')
|
|
||||||
.setDescription('The message you want to send me.')
|
|
||||||
.setRequired(true)),
|
|
||||||
|
|
||||||
new SlashCommandBuilder()
|
|
||||||
.setName('inspirobot')
|
|
||||||
.setDescription('Get an image from inspirobot'),
|
|
||||||
|
|
||||||
new SlashCommandBuilder()
|
|
||||||
.setName('tweet')
|
|
||||||
.setDescription('Send tweet from Haha yes twitter account. Please do not use it for advertisement and keep it english')
|
|
||||||
.addStringOption(option =>
|
|
||||||
option.setName('content')
|
|
||||||
.setDescription('The content of the tweet you want to send me.')
|
|
||||||
.setRequired(false))
|
|
||||||
.addAttachmentOption(option =>
|
|
||||||
option.setName('image')
|
|
||||||
.setDescription('Optional attachment (Image only.)')
|
|
||||||
.setRequired(false)),
|
|
||||||
|
|
||||||
new SlashCommandBuilder()
|
|
||||||
.setName('4chan')
|
|
||||||
.setDescription('Send random images from a 4chan board of your choosing!')
|
|
||||||
.addStringOption(option =>
|
|
||||||
option.setName('board')
|
|
||||||
.setDescription('The board you wish to see')
|
|
||||||
.setRequired(true)),
|
|
||||||
|
|
||||||
new SlashCommandBuilder()
|
|
||||||
.setName('donator')
|
|
||||||
.setDescription('All the people who donated for this bot <3'),
|
|
||||||
|
|
||||||
new SlashCommandBuilder()
|
|
||||||
.setName('donate')
|
|
||||||
.setDescription('Show donation link for the bot.'),
|
|
||||||
|
|
||||||
new SlashCommandBuilder()
|
|
||||||
.setName('about')
|
|
||||||
.setDescription('About me (The bot)'),
|
|
||||||
|
|
||||||
new SlashCommandBuilder()
|
|
||||||
.setName('stats')
|
|
||||||
.setDescription('Show some stats about the bot'),
|
|
||||||
|
|
||||||
new SlashCommandBuilder()
|
|
||||||
.setName('fakeuser')
|
|
||||||
.setDescription('Fake a user with webhooks')
|
|
||||||
.addMentionableOption(option =>
|
|
||||||
option.setName('user')
|
|
||||||
.setDescription('Who do you want to fake?')
|
|
||||||
.setRequired(true))
|
|
||||||
.addStringOption(option =>
|
|
||||||
option.setName('message')
|
|
||||||
.setDescription('What message do you want me to send?')
|
|
||||||
.setRequired(true))
|
|
||||||
.addAttachmentOption(option =>
|
|
||||||
option.setName('image')
|
|
||||||
.setDescription('Optional attachment (Image only.)')
|
|
||||||
.setRequired(false)),
|
|
||||||
|
|
||||||
new SlashCommandBuilder()
|
|
||||||
.setName('s')
|
|
||||||
.setDescription('What could this be 🤫')
|
|
||||||
.addStringOption(option =>
|
|
||||||
option.setName('something')
|
|
||||||
.setDescription('🤫')
|
|
||||||
.setRequired(true)),
|
|
||||||
]
|
|
||||||
.map(command => command.toJSON());
|
|
||||||
|
|
||||||
const rest = new REST({ version: '9' }).setToken(token);
|
|
||||||
|
|
||||||
if (process.argv[2] === 'global') {
|
|
||||||
rest.put(Routes.applicationCommands(clientId), { body: commands })
|
|
||||||
.then(() => console.log('Successfully registered application commands globally.'))
|
|
||||||
.catch(console.error);
|
|
||||||
}
|
|
||||||
else if (process.argv[2] === 'delete') {
|
|
||||||
rest.put(Routes.applicationGuildCommands(clientId, guildId), { body: [] })
|
|
||||||
.then(() => console.log('Successfully deleted all guild commands.'))
|
|
||||||
.catch(console.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
rest.put(Routes.applicationGuildCommands(clientId, guildId), { body: commands })
|
|
||||||
.then(() => console.log(`Successfully registered application commands for the guild ${guildId}.`))
|
|
||||||
.catch(console.error);
|
|
42
scripts/deploy-commands.js
Normal file
42
scripts/deploy-commands.js
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import { REST } from '@discordjs/rest';
|
||||||
|
import { Routes } from 'discord-api-types/v9';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||||
|
const { clientId, guildId, token } = process.env;
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const commands = [];
|
||||||
|
|
||||||
|
const categoryPath = fs.readdirSync(`${__dirname}/../commands`);
|
||||||
|
for (let i = 0; i < categoryPath.length; i++) {
|
||||||
|
const commandsPath = path.join(`${__dirname}/../commands`, categoryPath[i]);
|
||||||
|
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
|
||||||
|
for (const file of commandFiles) {
|
||||||
|
const filePath = path.join(commandsPath, file);
|
||||||
|
const command = await import(pathToFileURL(filePath));
|
||||||
|
|
||||||
|
if (command.default.integration_types) {
|
||||||
|
Object.assign(command.default.data, { integration_types: command.default.integration_types });
|
||||||
|
Object.assign(command.default.data, { contexts: [0, 1, 2] });
|
||||||
|
}
|
||||||
|
commands.push(command.default.data.toJSON());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rest = new REST({ version: '9' }).setToken(token);
|
||||||
|
if (process.argv[2] === 'global') {
|
||||||
|
rest.put(Routes.applicationCommands(clientId), { body: commands })
|
||||||
|
.then(() => console.log('Successfully registered application commands globally.'))
|
||||||
|
.catch(console.error);
|
||||||
|
}
|
||||||
|
else if (process.argv[2] === 'delete') {
|
||||||
|
rest.put(Routes.applicationGuildCommands(clientId, guildId), { body: [] })
|
||||||
|
.then(() => console.log('Successfully deleted all guild commands.'))
|
||||||
|
.catch(console.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
rest.put(Routes.applicationGuildCommands(clientId, guildId), { body: commands })
|
||||||
|
.then(() => console.log(`Successfully registered application commands for the guild ${guildId}.`))
|
||||||
|
.catch(console.error);
|
|
@ -1,42 +0,0 @@
|
||||||
const { SlashCommandBuilder } = require('@discordjs/builders');
|
|
||||||
const { REST } = require('@discordjs/rest');
|
|
||||||
const { Routes } = require('discord-api-types/v9');
|
|
||||||
require('dotenv').config();
|
|
||||||
const { clientId, guildId, token } = process.env;
|
|
||||||
|
|
||||||
const commands = [
|
|
||||||
new SlashCommandBuilder()
|
|
||||||
.setName('die')
|
|
||||||
.setDescription('Kill the bot'),
|
|
||||||
|
|
||||||
new SlashCommandBuilder()
|
|
||||||
.setName('ublacklist')
|
|
||||||
.setDescription('Blacklist a user from the bot')
|
|
||||||
.addStringOption(option =>
|
|
||||||
option.setName('command')
|
|
||||||
.setDescription('Which command do you want to get a user blacklisted from?')
|
|
||||||
.setRequired(true))
|
|
||||||
.addStringOption(option =>
|
|
||||||
option.setName('userid')
|
|
||||||
.setDescription('Who do you want to blacklist?')
|
|
||||||
.setRequired(true))
|
|
||||||
.addStringOption(option =>
|
|
||||||
option.setName('reason')
|
|
||||||
.setDescription('The reason of the blacklist.')
|
|
||||||
.setRequired(false)),
|
|
||||||
|
|
||||||
new SlashCommandBuilder()
|
|
||||||
.setName('deletewteet')
|
|
||||||
.setDescription('Delete a tweet')
|
|
||||||
.addStringOption(option =>
|
|
||||||
option.setName('tweetid')
|
|
||||||
.setDescription('The id of the tweet you wish to delete.')
|
|
||||||
.setRequired(true)),
|
|
||||||
]
|
|
||||||
.map(command => command.toJSON());
|
|
||||||
|
|
||||||
const rest = new REST({ version: '9' }).setToken(token);
|
|
||||||
|
|
||||||
rest.put(Routes.applicationGuildCommands(clientId, guildId), { body: commands })
|
|
||||||
.then(() => console.log(`Successfully registered application commands for the guild ${guildId}.`))
|
|
||||||
.catch(console.error);
|
|
34
scripts/downloadutils.js
Normal file
34
scripts/downloadutils.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import https from 'node:https';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
download,
|
||||||
|
};
|
||||||
|
|
||||||
|
async function download(url, output) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
https.get(url, (res) => {
|
||||||
|
if (res.statusCode === 301 || res.statusCode === 302) {
|
||||||
|
console.log(`${output} download url: ${res.headers.location}`);
|
||||||
|
return download(res.headers.location, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = output;
|
||||||
|
const tmpPath = `${output}.new`;
|
||||||
|
|
||||||
|
const filePath = fs.createWriteStream(tmpPath);
|
||||||
|
res.pipe(filePath);
|
||||||
|
filePath.on('finish', () => {
|
||||||
|
filePath.close();
|
||||||
|
fs.renameSync(tmpPath, path);
|
||||||
|
fs.chmodSync(path, '755');
|
||||||
|
console.log(`${url} download finished.`);
|
||||||
|
return resolve(true);
|
||||||
|
});
|
||||||
|
filePath.on('error', (err) => {
|
||||||
|
filePath.close();
|
||||||
|
return reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
27
scripts/updateBots.ggStats.js
Normal file
27
scripts/updateBots.ggStats.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import fetch from 'node-fetch';
|
||||||
|
import { Client, GatewayIntentBits } from 'discord.js';
|
||||||
|
|
||||||
|
const { botsggToken, botsggEndpoint, token } = process.env;
|
||||||
|
|
||||||
|
const client = new Client({
|
||||||
|
intents: [GatewayIntentBits.Guilds],
|
||||||
|
});
|
||||||
|
await client.login(token);
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
guildCount: client.guilds.cache.size,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(body);
|
||||||
|
|
||||||
|
const response = await fetch(`${botsggEndpoint}/bots/${client.user.id}/stats`, {
|
||||||
|
method: 'post',
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
headers: { 'Authorization': botsggToken, 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
process.exit();
|
|
@ -1,41 +1,17 @@
|
||||||
import fs from 'node:fs';
|
// 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 https from 'node:https';
|
import utils from './downloadutils.js';
|
||||||
|
|
||||||
if (process.platform !== 'linux' && process.argv[2] !== '-f') {
|
(async () => {
|
||||||
console.error('This script only download the linux version of yt-dlp. If you want to download anyway try again with -f');
|
if (process.platform !== 'linux' && process.argv[2] !== '-f') {
|
||||||
process.exit(1);
|
console.error('This script only download the linux version of yt-dlp. If you want to download anyway try again with -f or execute ./bin/yt-dlp --update-to nightly');
|
||||||
}
|
process.exit(1);
|
||||||
else if (process.platform !== 'linux' && process.argv[2] === '-f') {
|
}
|
||||||
console.log('Executed with -f. Reminder that this script only download the linux version of yt-dlp.');
|
else if (process.platform !== 'linux' && process.argv[2] === '-f') {
|
||||||
}
|
console.log('Executed with -f. Reminder that this script only download the linux version of yt-dlp.');
|
||||||
|
}
|
||||||
|
|
||||||
const downloadUrl = 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp';
|
const downloadUrl = 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp';
|
||||||
|
|
||||||
download(downloadUrl);
|
await utils.download(downloadUrl, './bin/yt-dlp');
|
||||||
|
|
||||||
async function download(url) {
|
});
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
https.get(url, (res) => {
|
|
||||||
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
||||||
console.log(`yt-dlp download url: ${res.headers.location}`);
|
|
||||||
return download(res.headers.location);
|
|
||||||
}
|
|
||||||
|
|
||||||
const tmpPath = './bin/yt-dlp.new';
|
|
||||||
const path = './bin/yt-dlp';
|
|
||||||
const filePath = fs.createWriteStream(tmpPath);
|
|
||||||
res.pipe(filePath);
|
|
||||||
filePath.on('finish', () => {
|
|
||||||
filePath.close();
|
|
||||||
fs.renameSync(tmpPath, path);
|
|
||||||
fs.chmodSync('./bin/yt-dlp', '755');
|
|
||||||
console.log('yt-dlp download finished.');
|
|
||||||
resolve(true);
|
|
||||||
});
|
|
||||||
filePath.on('error', (err) => {
|
|
||||||
filePath.close();
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue