Compare commits
10
Commits
3858a7e265
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8cd9380166 | ||
|
|
623300ea4a | ||
|
|
2d29b80b50 | ||
|
|
55ebff6bf2 | ||
|
|
0304eba0b3 | ||
|
|
105dd2dc4d | ||
|
|
6d2a1a484f | ||
|
|
554ab445e5 | ||
|
|
f637673a4e | ||
|
|
1a5aa6abe5 |
No files matched your search
+5
-1
@@ -6,6 +6,8 @@ yarn-debug.log*
|
|||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
lerna-debug.log*
|
lerna-debug.log*
|
||||||
|
|
||||||
|
*.bat
|
||||||
|
|
||||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
@@ -139,4 +141,6 @@ dist
|
|||||||
|
|
||||||
# Vite logs files
|
# Vite logs files
|
||||||
vite.config.js.timestamp-*
|
vite.config.js.timestamp-*
|
||||||
vite.config.ts.timestamp-*
|
|
||||||
|
items.json
|
||||||
|
db.json
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"autoroles": {
|
"autoroles": {
|
||||||
"1133441692196950101": "1192595134026616894",
|
"1133441692196950101": "1192595134026616894"
|
||||||
"1487585267777933455": "1487585267777933459"
|
|
||||||
},
|
},
|
||||||
"autobans": {
|
"autobans": {
|
||||||
"1133441692196950101": "1264634623950848158"
|
"1133441692196950101": "1264634623950848158"
|
||||||
|
|||||||
-33208
File diff suppressed because it is too large.
Load diff
@@ -4,6 +4,7 @@ const { time } = require("discord.js");
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const logger = require('../../utils/logger');
|
const logger = require('../../utils/logger');
|
||||||
|
const env = process.env;
|
||||||
|
|
||||||
async function fetch_avatar(id) {
|
async function fetch_avatar(id) {
|
||||||
const res = await fetch(`https://thumbnails.roproxy.com/v1/users/avatar?userIds=${id}&size=420x420&format=Png&isCircular=false`, {
|
const res = await fetch(`https://thumbnails.roproxy.com/v1/users/avatar?userIds=${id}&size=420x420&format=Png&isCircular=false`, {
|
||||||
@@ -47,7 +48,7 @@ module.exports = {
|
|||||||
name: 'rbx',
|
name: 'rbx',
|
||||||
description: `various commands related to roblox`,
|
description: `various commands related to roblox`,
|
||||||
aliases: ['roblox'],
|
aliases: ['roblox'],
|
||||||
usage: 'rbx user <username>\n.rbx item <item id>\n.rbx group <group id>\n.rbx game <game id>\n.rbx avatar <username>',
|
usage: 'rbx user <username>\n.rbx item <item id>\n.rbx group <group id>\n.rbx game <game id>',
|
||||||
|
|
||||||
async execute(client, message) {
|
async execute(client, message) {
|
||||||
var embed = {};
|
var embed = {};
|
||||||
@@ -91,49 +92,46 @@ module.exports = {
|
|||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
|
|
||||||
let latency = Date.now() - message.createdTimestamp;
|
const res = await fetch(`https://users.roblox.com/v1/usernames/users`, {
|
||||||
let apiLatency = Math.round(client.ws.ping);
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.ROBLOSECURITY}` },
|
||||||
|
body: JSON.stringify({ usernames: [argument], excludeBannedUsers: false })
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
logger.debug(`[rbx] fetched user data (roblox, standard) for "${argument}": ${JSON.stringify(data)}`);
|
||||||
|
|
||||||
|
if (!data.data || !data.data[0]) {
|
||||||
|
throw new Error(`user ${argument} not found...`);
|
||||||
|
}
|
||||||
|
const extra_res = await fetch(`https://users.roblox.com/v1/users/${data.data[0].id}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.ROBLOSECURITY}` }
|
||||||
|
});
|
||||||
|
const extra_data = await extra_res.json();
|
||||||
|
logger.debug(`[rbx] fetched user data (roblox, extra) for "${argument}": ${JSON.stringify(extra_data)}`);
|
||||||
|
|
||||||
|
const r_res = await fetch(`https://api.rolimons.com/players/v1/playerinfo/${data.data[0].id}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
let r_data = {};
|
||||||
|
if (r_res.ok) {
|
||||||
|
r_data = await r_res.json();
|
||||||
|
} else {
|
||||||
|
logger.warn(`[rbx] Failed to fetch rolimons playerinfo for id ${data.data[0].id}: HTTP ${r_res.status}`);
|
||||||
|
r_data = {};
|
||||||
|
}
|
||||||
|
logger.debug(`[rbx] fetched user data (rolimons) for "${argument}": ${JSON.stringify(r_data)}`);
|
||||||
|
|
||||||
|
const ra_res = await fetch(`https://api.rolimons.com/players/v1/playerassets/${data.data[0].id}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
const ra_data = await ra_res.json();
|
||||||
|
logger.debug(`[rbx] fetched user data (rolimons, assets) for "${argument}": ${JSON.stringify(ra_data)}`);
|
||||||
|
|
||||||
if (subcommand === 'user' || subcommand === 'u') {
|
if (subcommand === 'user' || subcommand === 'u') {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`https://users.roproxy.com/v1/usernames/users`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.ROBLOSECURITY}` },
|
|
||||||
body: JSON.stringify({ usernames: [argument], excludeBannedUsers: false })
|
|
||||||
});
|
|
||||||
const data = await res.json();
|
|
||||||
logger.debug(`[rbx] fetched user data (roblox, standard) for "${argument}": ${JSON.stringify(data)}`);
|
|
||||||
|
|
||||||
if (!data.data || !data.data[0]) {
|
|
||||||
throw new Error('User not found');
|
|
||||||
}
|
|
||||||
const extra_res = await fetch(`https://users.roproxy.com/v1/users/${data.data[0].id}`, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.ROBLOSECURITY}` }
|
|
||||||
});
|
|
||||||
const extra_data = await extra_res.json();
|
|
||||||
logger.debug(`[rbx] fetched user data (roblox, extra) for "${argument}": ${JSON.stringify(extra_data)}`);
|
|
||||||
|
|
||||||
const r_res = await fetch(`https://api.rolimons.com/players/v1/playerinfo/${data.data[0].id}`, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
});
|
|
||||||
let r_data = {};
|
|
||||||
if (r_res.ok) {
|
|
||||||
r_data = await r_res.json();
|
|
||||||
} else {
|
|
||||||
logger.warn(`[rbx] Failed to fetch rolimons playerinfo for id ${data.data[0].id}: HTTP ${r_res.status}`);
|
|
||||||
r_data = {};
|
|
||||||
}
|
|
||||||
logger.debug(`[rbx] fetched user data (rolimons) for "${argument}": ${JSON.stringify(r_data)}`);
|
|
||||||
|
|
||||||
const ra_res = await fetch(`https://api.rolimons.com/players/v1/playerassets/${data.data[0].id}`, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
});
|
|
||||||
const ra_data = await ra_res.json();
|
|
||||||
logger.debug(`[rbx] fetched user data (rolimons, assets) for "${argument}": ${JSON.stringify(ra_data)}`);
|
|
||||||
|
|
||||||
if (!data.data || !data.data[0]) {
|
if (!data.data || !data.data[0]) {
|
||||||
embed = {
|
embed = {
|
||||||
color: 0x8aa2b3,
|
color: 0x8aa2b3,
|
||||||
@@ -208,43 +206,6 @@ module.exports = {
|
|||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (subcommand === 'avatar') {
|
|
||||||
try {
|
|
||||||
const res = await fetch(`https://thumbnails.roproxy.com/v1/users/avatar?userIds=${id}&size=420x420&format=Png&isCircular=false`, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
embed = {
|
|
||||||
color: 0x8aa2b3,
|
|
||||||
author: {
|
|
||||||
name: message.author.username,
|
|
||||||
icon_url: message.author.displayAvatarURL(),
|
|
||||||
},
|
|
||||||
description: `${}`,
|
|
||||||
footer: {
|
|
||||||
text: `twitter @ ${message.guild.name}`,
|
|
||||||
icon_url: client.user.displayAvatarURL(),
|
|
||||||
},
|
|
||||||
timestamp: new Date(),
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(`[rbx] failed to fetch avatar for user id "${argument}":`, err);
|
|
||||||
embed = {
|
|
||||||
color: 0x8aa2b3,
|
|
||||||
author: {
|
|
||||||
name: message.author.username,
|
|
||||||
icon_url: message.author.displayAvatarURL(),
|
|
||||||
},
|
|
||||||
description: `<:no:1481811955592466492> could not fetch avatar data for user "${argument}"!`,
|
|
||||||
footer: {
|
|
||||||
text: `twitter @ ${message.guild.name}`,
|
|
||||||
icon_url: client.user.displayAvatarURL(),
|
|
||||||
},
|
|
||||||
timestamp: new Date(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else if (subcommand === 'item') {
|
} else if (subcommand === 'item') {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`https://economy.roproxy.com/v2/assets/${argument}/details`, {
|
const res = await fetch(`https://economy.roproxy.com/v2/assets/${argument}/details`, {
|
||||||
|
|||||||
+11
-3
@@ -1,7 +1,12 @@
|
|||||||
const { EmbedBuilder } = require('discord.js');
|
const { EmbedBuilder } = require('discord.js');
|
||||||
|
const db = require('../../db.json');
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const get_guild_prefix = (guildId) => {
|
||||||
|
return db.prefixes?.[guildId] || null;
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
name: 'help',
|
name: 'help',
|
||||||
description: 'lists all available commands or shows info about a specific one',
|
description: 'lists all available commands or shows info about a specific one',
|
||||||
@@ -10,9 +15,13 @@ module.exports = {
|
|||||||
|
|
||||||
execute(client, message, args) {
|
execute(client, message, args) {
|
||||||
var embed;
|
var embed;
|
||||||
const prefix = process.env.PREFIX || '!';
|
|
||||||
|
|
||||||
// Show info for a specific command
|
let prefix = process.env.PREFIX;
|
||||||
|
if (message.guild) {
|
||||||
|
const guildPrefix = get_guild_prefix(message.guild.id);
|
||||||
|
if (guildPrefix) prefix = guildPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
if (args.length > 0) {
|
if (args.length > 0) {
|
||||||
const name = args[0].toLowerCase();
|
const name = args[0].toLowerCase();
|
||||||
const command = client.commands.get(name);
|
const command = client.commands.get(name);
|
||||||
@@ -43,7 +52,6 @@ module.exports = {
|
|||||||
return message.reply({ embeds: [embed] });
|
return message.reply({ embeds: [embed] });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build a deduplicated command list (ignore aliases pointing to the same command)
|
|
||||||
const seen = new Set();
|
const seen = new Set();
|
||||||
const commandList = [];
|
const commandList = [];
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ const { time } = require("discord.js");
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
name: 'ban',
|
name: 'ban',
|
||||||
description: `ban a user from the current server`,
|
description: `ban a user from the current server`,
|
||||||
aliases: ['b'],
|
aliases: ['banish'],
|
||||||
usage: 'ban <user> [reason] [duration]',
|
usage: 'ban <user> [reason] [duration]',
|
||||||
|
|
||||||
async execute(client, message, args) {
|
async execute(client, message, args) {
|
||||||
@@ -47,7 +47,6 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const reason = args.slice(1).join(' ') || 'no reason provided (twitter)';
|
const reason = args.slice(1).join(' ') || 'no reason provided (twitter)';
|
||||||
const duration = args[2] ? parseInt(args[2]) : null;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await message.guild.members.ban(user, { reason });
|
await message.guild.members.ban(user, { reason });
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { time } = require("discord.js");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'lockdown',
|
||||||
|
description: 'lockdown the the current channel ',
|
||||||
|
aliases: ['ld', 'lock'],
|
||||||
|
usage: 'lockdown',
|
||||||
|
|
||||||
|
async execute(client, message) {
|
||||||
|
var embed;
|
||||||
|
|
||||||
|
if (!message.member?.permissions?.has('ManageChannels')) {
|
||||||
|
await message.reply({
|
||||||
|
embeds: [{
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
title: '',
|
||||||
|
description: '<:no:1481811955592466492> you do not have permissions to run this command!',
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild.name}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const channel = message.channel;
|
||||||
|
await channel.permissionOverwrites.edit(message.guild.roles.everyone, { SendMessages: false });
|
||||||
|
await channel.permissionOverwrites.edit(message.guild.roles.cache.find(r => r.permissions.has('Administrator')), { SendMessages: true });
|
||||||
|
embed = {
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
title: '',
|
||||||
|
description: `<:check:1481811973447356496> ${channel} is now locked!`,
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild.name}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
await message.reply({ content: null, embeds: [embed] });
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { time } = require("discord.js");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'role',
|
||||||
|
description: `give or take a role from a user`,
|
||||||
|
aliases: ['r'],
|
||||||
|
usage: 'role <user> (-)<role>',
|
||||||
|
|
||||||
|
async execute(client, message) {
|
||||||
|
var embed = {
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
title: '',
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild.name}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!message.member.permissions.has('Administrator')) {
|
||||||
|
embed.description = '<:no:1481811955592466492> you do not have permissions to run this command!';
|
||||||
|
await message.reply({ content: null, embeds: [embed] });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!message.mentions.members.size) {
|
||||||
|
embed.description = '<:no:1481811955592466492> please specify a user to give or take a role from!';
|
||||||
|
await message.reply({ content: null, embeds: [embed] });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const member = message.mentions.members.first();
|
||||||
|
const roleName = message.content.split(' ').slice(2).join(' ').replace(/^-/, '');
|
||||||
|
const role = message.guild.roles.cache.find(r => r.name === roleName);
|
||||||
|
|
||||||
|
if (!role) {
|
||||||
|
embed.description = '<:no:1481811955592466492> role not found!';
|
||||||
|
await message.reply({ content: null, embeds: [embed] });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.content.split(' ')[2].startsWith('-')) {
|
||||||
|
if (!member.roles.cache.has(role.id)) {
|
||||||
|
embed.description = `<:no:1481811955592466492> ${member} does not have the ${role} role!`;
|
||||||
|
await message.reply({ content: null, embeds: [embed] });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await member.roles.remove(role);
|
||||||
|
embed.description = `<:check:1481811973447356496> ${role} role has been taken from ${member}!`;
|
||||||
|
} else {
|
||||||
|
if (member.roles.cache.has(role.id)) {
|
||||||
|
embed.description = `<:no:1481811955592466492> ${member} already has the ${role} role!`;
|
||||||
|
await message.reply({ content: null, embeds: [embed] });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await member.roles.add(role);
|
||||||
|
embed.description = `<:check:1481811973447356496> ${role} role has been given to ${member}!`;
|
||||||
|
}
|
||||||
|
|
||||||
|
await message.reply({ content: null, embeds: [embed] });
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { PermissionFlagsBits } = require("discord.js");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'tempban',
|
||||||
|
description: `temporarily ban a user from the current server`,
|
||||||
|
aliases: ['tban'],
|
||||||
|
usage: 'tempban <user> <duration> [reason]',
|
||||||
|
|
||||||
|
async execute(client, message, args) {
|
||||||
|
if (!message.member.permissions.has('BanMembers')) {
|
||||||
|
return message.reply({
|
||||||
|
content: null, embeds: [{
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
author: {
|
||||||
|
name: message.author.username,
|
||||||
|
icon_url: message.author.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
description: '<:no:1481811955592466492> you do not have permission to use this command!',
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild.name}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = message.mentions.users.first() || client.users.cache.get(args[0]);
|
||||||
|
if (!user) {
|
||||||
|
return message.reply({
|
||||||
|
content: null, embeds: [{
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
author: {
|
||||||
|
name: message.author.username,
|
||||||
|
icon_url: message.author.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
description: '<:no:1481811955592466492> please specify a user to ban!',
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild.name}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const durationStr = args[1];
|
||||||
|
const durationMap = { s: 1000, m: 60000, h: 3600000, d: 86400000, w: 604800000 };
|
||||||
|
const match = durationStr?.match(/^(\d+)([smhdw])$/);
|
||||||
|
|
||||||
|
if (!match || !durationMap[match[2]]) {
|
||||||
|
return message.reply({
|
||||||
|
content: null, embeds: [{
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
author: {
|
||||||
|
name: message.author.username,
|
||||||
|
icon_url: message.author.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
description: '<:no:1481811955592466492> please specify a valid duration (e.g., 1s, 1m, 1d, 1w)!',
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild.name}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const duration = parseInt(match[1]) * durationMap[match[2]];
|
||||||
|
const reason = args.slice(2).join(' ') || 'no reason provided (twitter)';
|
||||||
|
|
||||||
|
try {
|
||||||
|
await message.guild.members.ban(user, { reason: `${reason} (twitter, moderator: ${message.author.username})` });
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
message.guild.bans.remove(user, 'temporary ban expired (twitter)');
|
||||||
|
}, duration);
|
||||||
|
|
||||||
|
message.reply({
|
||||||
|
content: null, embeds: [{
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
author: {
|
||||||
|
name: message.author.username,
|
||||||
|
icon_url: message.author.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
description: `<:check:1481811973447356496> ${user.username} has been temporarily banned for ${durationStr}!`,
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild.name}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
message.reply({
|
||||||
|
content: null, embeds: [{
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
author: {
|
||||||
|
name: message.author.username,
|
||||||
|
icon_url: message.author.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
description: '<:no:1481811955592466492> an error occurred while trying to ban the user!',
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild.name}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { time } = require("discord.js");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'timeout',
|
||||||
|
description: `timeout a user`,
|
||||||
|
aliases: ['mute'],
|
||||||
|
usage: 'timeout <user> <duration>',
|
||||||
|
|
||||||
|
async execute(client, message, args) {
|
||||||
|
var embed;
|
||||||
|
|
||||||
|
if (!message.member.permissions.has('ModerateMembers')) {
|
||||||
|
await message.reply({
|
||||||
|
content: null, embeds: [{
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
title: '',
|
||||||
|
description: '<:no:1481811955592466492> you do not have permissions to run this command!',
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild.name}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!message.mentions.members.size) {
|
||||||
|
await message.reply({
|
||||||
|
content: null, embeds: [{
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
title: '',
|
||||||
|
description: '<:no:1481811955592466492> please specify a user to timeout!',
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild.name}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const member = message.mentions.members.first();
|
||||||
|
|
||||||
|
const durationStr = args[1];
|
||||||
|
const durationMap = { s: 1000, m: 60000, h: 3600000, d: 86400000, w: 604800000 };
|
||||||
|
const match = durationStr?.match(/^(\d+)([smhdw])$/);
|
||||||
|
|
||||||
|
if (!match || !durationMap[match[2]]) {
|
||||||
|
return message.reply({
|
||||||
|
content: null, embeds: [{
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
author: {
|
||||||
|
name: message.author.username,
|
||||||
|
icon_url: message.author.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
description: '<:no:1481811955592466492> please specify a valid duration (e.g., 1s, 1m, 1d, 1w)!',
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild.name}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const duration = parseInt(match[1]) * durationMap[match[2]];
|
||||||
|
|
||||||
|
if (!duration) {
|
||||||
|
await message.reply({
|
||||||
|
content: null, embeds: [{
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
title: '',
|
||||||
|
description: '<:no:1481811955592466492> please specify a valid duration for the timeout!',
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild.name}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await member.timeout(duration);
|
||||||
|
embed = {
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
title: '',
|
||||||
|
description: `<:check:1481811973447356496> ${member} has been timed out for ${durationStr}!`,
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild.name}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
embed = {
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
title: '',
|
||||||
|
description: '<:no:1481811955592466492> an error occurred while trying to timeout the user!',
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild.name}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await message.reply({ content: null, embeds: [embed] });
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { time } = require("discord.js");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'unlockdown',
|
||||||
|
description: 'unlock the current channel ',
|
||||||
|
aliases: ['ud', 'unlock'],
|
||||||
|
usage: 'unlockdown',
|
||||||
|
|
||||||
|
async execute(client, message) {
|
||||||
|
var embed;
|
||||||
|
|
||||||
|
if (!message.member?.permissions?.has('ManageChannels')) {
|
||||||
|
await message.reply({
|
||||||
|
embeds: [{
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
title: '',
|
||||||
|
description: '<:no:1481811955592466492> you do not have permissions to run this command!',
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild.name}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const channel = message.channel;
|
||||||
|
|
||||||
|
await channel.permissionOverwrites.edit(
|
||||||
|
message.guild.roles.everyone,
|
||||||
|
{ SendMessages: null }
|
||||||
|
);
|
||||||
|
|
||||||
|
embed = {
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
title: '',
|
||||||
|
description: `<:check:1481811973447356496> ${channel} is now unlocked!`,
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild.name}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
await message.reply({ content: null, embeds: [embed] });
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { time } = require("discord.js");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'untimeout',
|
||||||
|
description: `untimeout a user`,
|
||||||
|
aliases: ['unmute'],
|
||||||
|
usage: 'untimeout <user>',
|
||||||
|
|
||||||
|
async execute(client, message, args) {
|
||||||
|
var embed;
|
||||||
|
|
||||||
|
if (!message.member.permissions.has('ModerateMembers')) {
|
||||||
|
await message.reply({
|
||||||
|
content: null, embeds: [{
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
title: '',
|
||||||
|
description: '<:no:1481811955592466492> you do not have permissions to run this command!',
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild.name}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!message.mentions.members.size || !message.mentions.members.first().communicationDisabledUntil) {
|
||||||
|
await message.reply({
|
||||||
|
content: null, embeds: [{
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
title: '',
|
||||||
|
description: '<:no:1481811955592466492> that user is not timed out!',
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild.name}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!message.mentions.members.size) {
|
||||||
|
await message.reply({
|
||||||
|
content: null, embeds: [{
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
title: '',
|
||||||
|
description: '<:no:1481811955592466492> please specify a user to timeout!',
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild.name}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const member = message.mentions.members.first();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await member.timeout(null);
|
||||||
|
embed = {
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
title: '',
|
||||||
|
description: `<:check:1481811973447356496> ${member} has been untimed out!`,
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild.name}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
embed = {
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
title: '',
|
||||||
|
description: '<:no:1481811955592466492> an error occurred while trying to untimeout the user!',
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild.name}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await message.reply({ content: null, embeds: [embed] });
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,370 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const fsp = require('fs/promises');
|
||||||
|
const os = require('os');
|
||||||
|
const path = require('path');
|
||||||
|
const { spawn } = require('child_process');
|
||||||
|
const { pipeline } = require('stream/promises');
|
||||||
|
const { Readable } = require('stream');
|
||||||
|
const logger = require('../../utils/logger');
|
||||||
|
|
||||||
|
function isMp3Attachment(attachment) {
|
||||||
|
const name = (attachment.name || '').toLowerCase();
|
||||||
|
const contentType = (attachment.contentType || '').toLowerCase();
|
||||||
|
|
||||||
|
return name.endsWith('.mp3') || contentType.startsWith('audio/');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isImageAttachment(attachment) {
|
||||||
|
const name = (attachment.name || '').toLowerCase();
|
||||||
|
const contentType = (attachment.contentType || '').toLowerCase();
|
||||||
|
|
||||||
|
return (
|
||||||
|
name.endsWith('.png') ||
|
||||||
|
name.endsWith('.jpg') ||
|
||||||
|
name.endsWith('.jpeg') ||
|
||||||
|
name.endsWith('.webp') ||
|
||||||
|
(contentType.startsWith('image/') && contentType !== 'image/gif')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isGifAttachment(attachment) {
|
||||||
|
const name = (attachment.name || '').toLowerCase();
|
||||||
|
const contentType = (attachment.contentType || '').toLowerCase();
|
||||||
|
|
||||||
|
return name.endsWith('.gif') || contentType === 'image/gif';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isVideoAttachment(attachment) {
|
||||||
|
const name = (attachment.name || '').toLowerCase();
|
||||||
|
const contentType = (attachment.contentType || '').toLowerCase();
|
||||||
|
|
||||||
|
return (
|
||||||
|
name.endsWith('.mp4') ||
|
||||||
|
name.endsWith('.mov') ||
|
||||||
|
name.endsWith('.mkv') ||
|
||||||
|
name.endsWith('.webm') ||
|
||||||
|
name.endsWith('.m4v') ||
|
||||||
|
contentType.startsWith('video/')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function runProcess(command, args) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const child = spawn(command, args, { stdio: ['ignore', 'ignore', 'pipe'] });
|
||||||
|
let stderr = '';
|
||||||
|
|
||||||
|
child.stderr.on('data', (chunk) => {
|
||||||
|
stderr += chunk.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on('error', reject);
|
||||||
|
child.on('close', (code) => {
|
||||||
|
if (code === 0) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reject(new Error(stderr || `${command} exited with code ${code}`));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRetryableFetchError(err) {
|
||||||
|
const code = err?.code || err?.cause?.code;
|
||||||
|
const name = err?.name;
|
||||||
|
const message = String(err?.message || '');
|
||||||
|
|
||||||
|
return (
|
||||||
|
code === 'UND_ERR_SOCKET' ||
|
||||||
|
code === 'ECONNRESET' ||
|
||||||
|
code === 'EPIPE' ||
|
||||||
|
code === 'ETIMEDOUT' ||
|
||||||
|
name === 'SocketError' ||
|
||||||
|
name === 'FetchError' ||
|
||||||
|
/other side closed/i.test(message)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sleep(ms) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadAttachment(attachment, filePath) {
|
||||||
|
const attempts = 3;
|
||||||
|
|
||||||
|
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(attachment.url);
|
||||||
|
|
||||||
|
if (!response.ok || !response.body) {
|
||||||
|
throw new Error(`failed to download ${attachment.name}: HTTP ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await pipeline(Readable.fromWeb(response.body), fs.createWriteStream(filePath));
|
||||||
|
return;
|
||||||
|
} catch (err) {
|
||||||
|
await fsp.rm(filePath, { force: true }).catch(() => null);
|
||||||
|
|
||||||
|
if (attempt < attempts && isRetryableFetchError(err)) {
|
||||||
|
logger.warn(
|
||||||
|
`[snippet] attachment download failed, retrying (${attempt}/${attempts}) for ${attachment.name || 'unknown'}: ${err.message || err}`,
|
||||||
|
);
|
||||||
|
await sleep(250 * attempt);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`failed to download ${attachment.name}: ${err.message || err}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getDuration(filePath) {
|
||||||
|
const probe = spawn('ffprobe', [
|
||||||
|
'-v', 'error',
|
||||||
|
'-show_entries', 'format=duration',
|
||||||
|
'-of', 'default=noprint_wrappers=1:nokey=1',
|
||||||
|
filePath,
|
||||||
|
], { stdio: ['ignore', 'pipe', 'pipe'] });
|
||||||
|
|
||||||
|
let stdout = '';
|
||||||
|
let stderr = '';
|
||||||
|
|
||||||
|
probe.stdout.on('data', (chunk) => {
|
||||||
|
stdout += chunk.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
probe.stderr.on('data', (chunk) => {
|
||||||
|
stderr += chunk.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
const exitCode = await new Promise((resolve, reject) => {
|
||||||
|
probe.on('error', reject);
|
||||||
|
probe.on('close', resolve);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
throw new Error(stderr || 'failed to read media duration');
|
||||||
|
}
|
||||||
|
|
||||||
|
const duration = Number.parseFloat(stdout.trim());
|
||||||
|
if (!Number.isFinite(duration) || duration <= 0) {
|
||||||
|
throw new Error('invalid media duration');
|
||||||
|
}
|
||||||
|
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildEmbed(client, message, description, fields = []) {
|
||||||
|
const embed = {
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
title: '',
|
||||||
|
author: {
|
||||||
|
name: message.author.username,
|
||||||
|
icon_url: message.author.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
description,
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild?.name || 'dms'}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (fields.length > 0) {
|
||||||
|
embed.fields = fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
return embed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getErrorMessage(err) {
|
||||||
|
if (err instanceof Error) {
|
||||||
|
return err.stack || err.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof err === 'string') {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'unknown error';
|
||||||
|
}
|
||||||
|
|
||||||
|
const logoPath = path.resolve(__dirname, '../../../assets/logo.png');
|
||||||
|
|
||||||
|
function buildVideoFilter() {
|
||||||
|
return [
|
||||||
|
'[0:v]scale=trunc(iw/2)*2:trunc(ih/2)*2[base]',
|
||||||
|
'[2:v]format=rgba,colorchannelmixer=aa=0.5,scale=trunc(iw*0.12/2)*2:-1,rotate=(PI/2)*t:ow=rotw(iw):oh=roth(ih):c=none[logo]',
|
||||||
|
'[base][logo]overlay=x=W-w-16:y=H-h-16:shortest=1[v]',
|
||||||
|
].join(';');
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'snippet',
|
||||||
|
description: 'create a snippet with an image/video and an mp3',
|
||||||
|
usage: 'snippet <seconds> [mp3 attachment] [image or video attachment]',
|
||||||
|
|
||||||
|
async execute(client, message, args = []) {
|
||||||
|
const requestedLength = Number.parseInt(args[0], 10);
|
||||||
|
|
||||||
|
if (!Number.isInteger(requestedLength) || requestedLength <= 0) {
|
||||||
|
return message.reply({
|
||||||
|
content: null,
|
||||||
|
embeds: [buildEmbed(
|
||||||
|
client,
|
||||||
|
message,
|
||||||
|
'<:no:1481811955592466492> provide a clip length in seconds, then attach one mp3 and either one image or one video file.',
|
||||||
|
)],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const attachments = [...message.attachments.values()];
|
||||||
|
const mp3Attachment = attachments.find(isMp3Attachment);
|
||||||
|
const imageAttachment = attachments.find(isImageAttachment);
|
||||||
|
const gifAttachment = attachments.find(isGifAttachment);
|
||||||
|
const videoAttachment = attachments.find(isVideoAttachment);
|
||||||
|
|
||||||
|
const visualAttachment = imageAttachment || gifAttachment || videoAttachment;
|
||||||
|
const hasMultipleVisualTypes = [imageAttachment, gifAttachment, videoAttachment].filter(Boolean).length > 1;
|
||||||
|
|
||||||
|
if (!mp3Attachment || !visualAttachment || hasMultipleVisualTypes) {
|
||||||
|
return message.reply({
|
||||||
|
content: null,
|
||||||
|
embeds: [buildEmbed(
|
||||||
|
client,
|
||||||
|
message,
|
||||||
|
'<:no:1481811955592466492> attach one mp3 file and either one image, one gif, or one video file, but not more than one visual file.',
|
||||||
|
)],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const sent = await message.reply({
|
||||||
|
content: null,
|
||||||
|
embeds: [buildEmbed(
|
||||||
|
client,
|
||||||
|
message,
|
||||||
|
'<a:loading_icon:1366587619890302998> building your snippet...',
|
||||||
|
)],
|
||||||
|
});
|
||||||
|
|
||||||
|
const tempDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'twitter-snippet-'));
|
||||||
|
const audioPath = path.join(tempDir, mp3Attachment.name || 'input.mp3');
|
||||||
|
const visualPath = path.join(tempDir, visualAttachment.name || (imageAttachment ? 'input.png' : gifAttachment ? 'input.gif' : 'input.mp4'));
|
||||||
|
const outputPath = path.join(tempDir, 'snippet.mp4');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Promise.all([
|
||||||
|
downloadAttachment(mp3Attachment, audioPath),
|
||||||
|
downloadAttachment(visualAttachment, visualPath),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const audioDuration = await getDuration(audioPath);
|
||||||
|
const isStillImageSource = Boolean(imageAttachment && imageAttachment === visualAttachment);
|
||||||
|
const isGifSource = Boolean(gifAttachment && gifAttachment === visualAttachment);
|
||||||
|
const isLoopedVisualSource = isStillImageSource || isGifSource;
|
||||||
|
const visualDuration = isLoopedVisualSource ? Infinity : await getDuration(visualPath);
|
||||||
|
|
||||||
|
if (requestedLength > audioDuration || requestedLength > visualDuration) {
|
||||||
|
throw new Error('requested length is longer than one of the attached files');
|
||||||
|
}
|
||||||
|
|
||||||
|
const clipLength = requestedLength;
|
||||||
|
|
||||||
|
if (isLoopedVisualSource) {
|
||||||
|
const audioStart = audioDuration === clipLength
|
||||||
|
? 0
|
||||||
|
: Math.random() * (audioDuration - clipLength);
|
||||||
|
|
||||||
|
await runProcess('ffmpeg', [
|
||||||
|
'-y',
|
||||||
|
...(isStillImageSource ? ['-loop', '1'] : ['-stream_loop', '-1']),
|
||||||
|
'-i', visualPath,
|
||||||
|
'-ss', audioStart.toFixed(3),
|
||||||
|
'-i', audioPath,
|
||||||
|
'-loop', '1',
|
||||||
|
'-i', logoPath,
|
||||||
|
'-t', clipLength.toFixed(3),
|
||||||
|
'-filter_complex', buildVideoFilter(),
|
||||||
|
'-map', '[v]',
|
||||||
|
'-map', '1:a:0',
|
||||||
|
'-c:v', 'libx264',
|
||||||
|
'-preset', 'veryfast',
|
||||||
|
'-crf', '20',
|
||||||
|
'-c:a', 'aac',
|
||||||
|
'-b:a', '192k',
|
||||||
|
'-pix_fmt', 'yuv420p',
|
||||||
|
'-movflags', '+faststart',
|
||||||
|
outputPath,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
const videoStart = visualDuration === clipLength
|
||||||
|
? 0
|
||||||
|
: Math.random() * (visualDuration - clipLength);
|
||||||
|
const audioStart = audioDuration === clipLength
|
||||||
|
? 0
|
||||||
|
: Math.random() * (audioDuration - clipLength);
|
||||||
|
|
||||||
|
await runProcess('ffmpeg', [
|
||||||
|
'-y',
|
||||||
|
'-ss', videoStart.toFixed(3),
|
||||||
|
'-i', visualPath,
|
||||||
|
'-ss', audioStart.toFixed(3),
|
||||||
|
'-i', audioPath,
|
||||||
|
'-loop', '1',
|
||||||
|
'-i', logoPath,
|
||||||
|
'-t', clipLength.toFixed(3),
|
||||||
|
'-filter_complex', buildVideoFilter(),
|
||||||
|
'-map', '[v]',
|
||||||
|
'-map', '1:a:0',
|
||||||
|
'-c:v', 'libx264',
|
||||||
|
'-preset', 'veryfast',
|
||||||
|
'-crf', '20',
|
||||||
|
'-c:a', 'aac',
|
||||||
|
'-b:a', '192k',
|
||||||
|
'-pix_fmt', 'yuv420p',
|
||||||
|
'-movflags', '+faststart',
|
||||||
|
outputPath,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalEmbed = buildEmbed(
|
||||||
|
client,
|
||||||
|
message,
|
||||||
|
'<:check:1481811973447356496> clip finished successfully.',
|
||||||
|
[
|
||||||
|
{ name: 'audio source', value: `**${mp3Attachment.name || 'unknown'}**`, inline: true },
|
||||||
|
{ name: 'visual source', value: `**${visualAttachment.name || 'unknown'}**`, inline: true },
|
||||||
|
{ name: 'clip length', value: `**${clipLength.toFixed(2)}s**`, inline: true },
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
await sent.edit({
|
||||||
|
content: null,
|
||||||
|
embeds: [finalEmbed],
|
||||||
|
files: [{ attachment: outputPath, name: 'snippet.mp4' }],
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
const errorMessage = getErrorMessage(err);
|
||||||
|
|
||||||
|
logger.error('[snippet] failed to build clip', err instanceof Error ? err : new Error(String(err)));
|
||||||
|
|
||||||
|
await sent.edit({
|
||||||
|
content: null,
|
||||||
|
embeds: [buildEmbed(
|
||||||
|
client,
|
||||||
|
message,
|
||||||
|
'<:no:1481811955592466492> failed to build the clip.',
|
||||||
|
[
|
||||||
|
{ name: 'reason', value: `**${errorMessage.slice(0, 1000)}**` },
|
||||||
|
],
|
||||||
|
)],
|
||||||
|
}).catch((editErr) => {
|
||||||
|
logger.error('[snippet] failed to edit error reply', editErr instanceof Error ? editErr : new Error(String(editErr)));
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
await fsp.rm(tempDir, { recursive: true, force: true }).catch(() => null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -61,6 +61,7 @@ module.exports = {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
await sent.edit({ content: null, embeds: [embed] });
|
await sent.edit({ content: null, embeds: [embed] });
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -9,7 +9,22 @@ module.exports = {
|
|||||||
usage: 'ping',
|
usage: 'ping',
|
||||||
|
|
||||||
async execute(client, message) {
|
async execute(client, message) {
|
||||||
const sent = await message.reply('pinging');
|
const sent = await message.reply({
|
||||||
|
content: null, embeds: [{
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
title: '',
|
||||||
|
author: {
|
||||||
|
name: message.author.username,
|
||||||
|
icon_url: message.author.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
description: '<a:loading_icon:1366587619890302998> pinging...',
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild.name}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
}]
|
||||||
|
});
|
||||||
const latency = sent.createdTimestamp - message.createdTimestamp;
|
const latency = sent.createdTimestamp - message.createdTimestamp;
|
||||||
const apiLatency = Math.round(client.ws.ping);
|
const apiLatency = Math.round(client.ws.ping);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { time } = require("discord.js");
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const path = require('node:path');
|
||||||
|
|
||||||
|
const DB_PATH = path.join(__dirname, '../../../db.json');
|
||||||
|
|
||||||
|
const readDb = () => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(fs.readFileSync(DB_PATH, 'utf8'));
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const writeDb = (db) => {
|
||||||
|
fs.writeFileSync(DB_PATH, JSON.stringify(db, null, 2), 'utf8');
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'prefix',
|
||||||
|
description: 'change the server command prefix',
|
||||||
|
aliases: ['setprefix'],
|
||||||
|
usage: 'prefix <new prefix>',
|
||||||
|
execute: async (client, message, args = []) => {
|
||||||
|
var embed;
|
||||||
|
if (!message.guild) {
|
||||||
|
await message.reply({
|
||||||
|
embeds: [{
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
title: '',
|
||||||
|
author: {
|
||||||
|
name: message.author.username,
|
||||||
|
icon_url: message.author.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
description: '<:no:1481811955592466492> this command can only be used in a server!',
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild.name}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date()
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!message.member?.permissions?.has('ManageGuild')) {
|
||||||
|
await message.reply({
|
||||||
|
embeds: [{
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
title: '',
|
||||||
|
author: {
|
||||||
|
name: message.author.username,
|
||||||
|
icon_url: message.author.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
description: '<:no:1481811955592466492> you do not have permissions to run this command!',
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild.name}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date()
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newPrefix = args[0];
|
||||||
|
if (!newPrefix) {
|
||||||
|
const parts = message.content.trim().split(/\s+/);
|
||||||
|
newPrefix = parts[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!newPrefix) {
|
||||||
|
await message.reply({
|
||||||
|
embeds: [{
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
title: '',
|
||||||
|
author: {
|
||||||
|
name: message.author.username,
|
||||||
|
icon_url: message.author.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
description: '<:no:1481811955592466492> you must provide a new prefix!',
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild.name}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date()
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPrefix.length > 5) {
|
||||||
|
await message.reply({
|
||||||
|
embeds: [{
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
title: '',
|
||||||
|
author: {
|
||||||
|
name: message.author.username,
|
||||||
|
icon_url: message.author.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
description: '<:no:1481811955592466492> prefix must be 5 characters or less!',
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild.name}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date()
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = readDb();
|
||||||
|
db.prefixes = db.prefixes || {};
|
||||||
|
db.prefixes[message.guild.id] = newPrefix;
|
||||||
|
writeDb(db);
|
||||||
|
|
||||||
|
await message.reply({
|
||||||
|
embeds: [{
|
||||||
|
color: 0x8aa2b3,
|
||||||
|
title: '',
|
||||||
|
author: {
|
||||||
|
name: message.author.username,
|
||||||
|
icon_url: message.author.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
description: `<:check:1481811973447356496> prefix for **${message.guild.name}** updated to \`${newPrefix}\`!`,
|
||||||
|
footer: {
|
||||||
|
text: `twitter @ ${message.guild.name}`,
|
||||||
|
icon_url: client.user.displayAvatarURL(),
|
||||||
|
},
|
||||||
|
timestamp: new Date()
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
void time;
|
||||||
@@ -2,18 +2,27 @@
|
|||||||
|
|
||||||
const { Events } = require('discord.js');
|
const { Events } = require('discord.js');
|
||||||
const logger = require('../utils/logger');
|
const logger = require('../utils/logger');
|
||||||
|
const db = require('../../db.json'); // adjust path as needed
|
||||||
|
|
||||||
|
const get_guild_prefix = (guildId) => {
|
||||||
|
return db.prefixes?.[guildId] || null;
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
name: Events.MessageCreate,
|
name: Events.MessageCreate,
|
||||||
|
|
||||||
async execute(client, message) {
|
async execute(client, message) {
|
||||||
// Ignore bots and messages without the prefix
|
|
||||||
if (message.author.bot) return;
|
if (message.author.bot) return;
|
||||||
|
|
||||||
const prefix = process.env.PREFIX || '!';
|
let prefix = process.env.PREFIX || '!';
|
||||||
|
|
||||||
|
if (message.guild) {
|
||||||
|
const guildPrefix = get_guild_prefix(message.guild.id);
|
||||||
|
if (guildPrefix) prefix = guildPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
if (!message.content.startsWith(prefix)) return;
|
if (!message.content.startsWith(prefix)) return;
|
||||||
|
|
||||||
// Parse command name and arguments
|
|
||||||
const args = message.content.slice(prefix.length).trim().split(/\s+/);
|
const args = message.content.slice(prefix.length).trim().split(/\s+/);
|
||||||
const commandName = args.shift().toLowerCase();
|
const commandName = args.shift().toLowerCase();
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ module.exports = {
|
|||||||
const variables = Object.keys(db);
|
const variables = Object.keys(db);
|
||||||
logger.info(`[db] loaded variables from db.json: ${variables.length > 0 ? variables.join(', ') : 'none'}`);
|
logger.info(`[db] loaded variables from db.json: ${variables.length > 0 ? variables.join(', ') : 'none'}`);
|
||||||
|
|
||||||
// cache rolimons item list
|
|
||||||
fetch('https://api.rolimons.com/items/v2/itemdetails')
|
fetch('https://api.rolimons.com/items/v2/itemdetails')
|
||||||
.then(res => res.ok ? res.json() : Promise.reject(new Error('Failed to fetch rolimons item list')))
|
.then(res => res.ok ? res.json() : Promise.reject(new Error('Failed to fetch rolimons item list')))
|
||||||
.then(data => {
|
.then(data => {
|
||||||
|
|||||||
@@ -5,15 +5,11 @@ const path = require('path');
|
|||||||
const logger = require('../utils/logger');
|
const logger = require('../utils/logger');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads all command files from src/commands and registers them on the client.
|
|
||||||
* Each command file must export: { name, description, aliases?, usage?, execute }
|
|
||||||
*
|
|
||||||
* @param {import('discord.js').Client} client
|
* @param {import('discord.js').Client} client
|
||||||
*/
|
*/
|
||||||
function loadCommands(client) {
|
function loadCommands(client) {
|
||||||
const commandsPath = path.join(__dirname, '..', 'commands');
|
const commandsPath = path.join(__dirname, '..', 'commands');
|
||||||
|
|
||||||
// Recursively get all .js files in commandsPath
|
|
||||||
function getAllCommandFiles(dir) {
|
function getAllCommandFiles(dir) {
|
||||||
let results = [];
|
let results = [];
|
||||||
const list = fs.readdirSync(dir, { withFileTypes: true });
|
const list = fs.readdirSync(dir, { withFileTypes: true });
|
||||||
@@ -45,7 +41,6 @@ function loadCommands(client) {
|
|||||||
|
|
||||||
client.commands.set(command.name.toLowerCase(), command);
|
client.commands.set(command.name.toLowerCase(), command);
|
||||||
|
|
||||||
// Register aliases if provided
|
|
||||||
if (Array.isArray(command.aliases)) {
|
if (Array.isArray(command.aliases)) {
|
||||||
for (const alias of command.aliases) {
|
for (const alias of command.aliases) {
|
||||||
client.commands.set(alias.toLowerCase(), command);
|
client.commands.set(alias.toLowerCase(), command);
|
||||||
|
|||||||
Reference in new issue
Block a user