Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,21 @@ Add new event handlers in `/src/events/` following the Discord.js v14 event stru
- Secure token management
- Audit trail for all actions


## 🔑 Webpanel Login

The webpanel is protected by a username/password login system. To create a user, use the console command while the bot is running:

```bash
webpaneluser <username> <password>
```

You can then log in at http://localhost:50249/login
webpaneluser Lacikika Lacko/0428
## 📝 TODO

See `TODO.md` for planned features and improvements.

## 📚 Documentation

Comprehensive documentation available in the `/documentation/` folder:
Expand Down
26 changes: 26 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# TODO - Nyx Discord Bot

## High Priority
- [✅] Add more advanced search filters to webpanel (by date, role, etc.)
- [ ] Add user and role management to webpanel
- [ ] Improve error handling and user feedback in webpanel
- [✅] Add rate limiting and brute-force protection to webpanel login
- [✅] Add multi-language support (i18n)
- [ ] Add tests for all major modules (commands, events, utils)

## Medium Priority
- [ ] Add API endpoints for bot stats and data
- [✅] Add Discord OAuth2 login for webpanel (optional)
- [ ] Add notification system for webpanel (success/error popups)
- [ ] Add dark/light theme toggle
- [ ] Add more analytics and charts to statistics page

## Low Priority
- [ ] Add command to export/import data
- [ ] Add Discord webhook integration for alerts
- [ ] Add more fun/utility commands
- [✅] Add more documentation examples

---

**Contributions and suggestions are welcome!**
44 changes: 44 additions & 0 deletions commands/entertainment/8ball.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Entertainment: 8ball.js
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const { readUser, writeUser, appendUserLog } = require('../../../utils/jsondb');

const responses = [
'Yes.', 'No.', 'Maybe.', 'Ask again later.', 'Definitely!', 'I don\'t think so.', 'Absolutely!', 'Not sure.'
];

module.exports = {
data: new SlashCommandBuilder()
.setName('8ball')
.setDescription('Ask the magic 8ball a question')
.addStringOption(option =>
option.setName('question').setDescription('Your question').setRequired(true)),
async execute(interaction) {
try {
const question = interaction.options.getString('question');
const response = responses[Math.floor(Math.random() * responses.length)];
const embed = new EmbedBuilder()
.setTitle('🎱 Varázsgömb')
.addFields(
{ name: 'Kérdés', value: question },
{ name: 'Válasz', value: response }
)
.setColor('Random')
.setThumbnail('https://cdn-icons-png.flaticon.com/512/616/616494.png')
.setFooter({ text: '⛏️ by Laci 🛠️' })
.setTimestamp();
// Log 8ball command usage
await appendUserLog('logs', interaction.user.id, interaction.guild.id, {
event_type: 'ENTERTAINMENT',
reason: `8ball: ${question}`,
warned_by: interaction.user.id,
channel_id: interaction.channel.id,
message_id: interaction.id,
message_content: question,
date: Date.now()
}, interaction.user.username);
await interaction.reply({ embeds: [embed] });
} catch (err) {
await interaction.reply({ content: 'There was an error executing this command. ' + (err.message || err), ephemeral: true });
}
},
};
38 changes: 38 additions & 0 deletions commands/entertainment/joke.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Entertainment: joke.js
const { SlashCommandBuilder } = require('discord.js');
const { readUser, writeUser, appendUserLog } = require('../../../utils/jsondb');
const { EmbedBuilder } = require('discord.js');

const jokes = [
'Why did the scarecrow win an award? Because he was outstanding in his field!',
'Why don\'t skeletons fight each other? They don\'t have the guts.',
'What do you call fake spaghetti? An impasta!'
];

module.exports = {
data: new SlashCommandBuilder()
.setName('joke')
.setDescription('Get a random joke'),
async execute(interaction) {
const joke = jokes[Math.floor(Math.random() * jokes.length)];
// Log joke command usage
await appendUserLog('logs', interaction.user.id, interaction.guild.id, {
event_type: 'ENTERTAINMENT',
reason: 'Joke command used',
warned_by: interaction.user.id,
channel_id: interaction.channel.id,
message_id: interaction.id,
message_content: joke,
date: Date.now()
}, interaction.user.username);

const embed = new EmbedBuilder()
.setTitle('📝 Vicc')
.setDescription(joke)
.setColor('Random')
.setThumbnail('https://cdn-icons-png.flaticon.com/512/616/616489.png')
.setFooter({ text: '⛏️ by Laci 🛠️' })
.setTimestamp();
await interaction.reply({ embeds: [embed] });
},
};
47 changes: 47 additions & 0 deletions commands/entertainment/meme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Example entertainment command: meme.js
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const https = require('https');
const { readUser, writeUser, appendUserLog } = require('../../../utils/jsondb');


module.exports = {
data: new SlashCommandBuilder()
.setName('meme')
.setDescription('Sends a random meme'),
async execute(interaction) {
// Fetch meme from API
https.get('https://meme-api.com/gimme', (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', async () => {
try {
const meme = JSON.parse(data);
const memeUrl = meme.url || 'https://i.imgur.com/8b7evkP.jpeg';
const embed = new EmbedBuilder()
.setTitle(meme.title || 'Véletlen mém')
.setImage(memeUrl)
.setURL(meme.postLink || memeUrl)
.setColor('Random')
.setFooter({ text: '⛏️ by Laci 🛠️' })
.setTimestamp();
await interaction.reply({ embeds: [embed] });

// Log meme command usage
await appendUserLog('logs', interaction.user.id, interaction.guild.id, {
event_type: 'ENTERTAINMENT',
reason: 'Meme command used',
warned_by: interaction.user.id,
channel_id: interaction.channel.id,
message_id: interaction.id,
message_content: meme.title || null,
date: Date.now()
}, interaction.user.username);
} catch (e) {
await interaction.reply({ content: 'Failed to fetch meme.', flags: 64 });
}
});
}).on('error', async () => {
await interaction.reply({ content: 'Failed to fetch meme.', flags: 64 });
});
},
};
70 changes: 70 additions & 0 deletions commands/moderation/ban.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Ban command
const { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder } = require('discord.js');
const { readUser, writeUser, appendUserLog } = require('../../../utils/jsondb');

module.exports = {
data: new SlashCommandBuilder()
.setName('ban')
.setDescription('Ban a member from the server')
.addUserOption(option =>
option.setName('target').setDescription('User to ban').setRequired(true)),
async execute(interaction) {
if (!interaction.member.permissions.has(PermissionFlagsBits.BanMembers) && !interaction.member.permissions.has(PermissionFlagsBits.Administrator)) {
const embed = new EmbedBuilder()
.setTitle('Nincs jogosultság')
.setDescription('Ehhez a parancshoz Ban Members vagy Admin jogosultság szükséges.')
.setColor('Red');
return interaction.reply({ embeds: [embed], flags: 64 });
}
const member = interaction.options.getMember('target');
if (!member.bannable) {
const embed = new EmbedBuilder()
.setTitle('Kitiltás sikertelen')
.setDescription('Ezt a felhasználót nem tudom kitiltani.')
.setColor('Red');
return interaction.reply({ embeds: [embed], flags: 64 });
}
await member.ban();
// Log ban to user log and update profile
const guildId = interaction.guild.id;
await appendUserLog('logs', member.id, guildId, {
event_type: 'BAN',
reason: 'Kitiltva parancs által',
warned_by: interaction.user.id,
channel_id: interaction.channel.id,
message_id: interaction.id,
message_content: null,
date: Date.now()
}, member.user.username);
const profile = await readUser('profiles', member.id, guildId);
profile.total_bans = (profile.total_bans || 0) + 1;
profile.last_seen = Date.now();
await writeUser('profiles', member.id, guildId, profile);
// Log to channel
const embed = new EmbedBuilder()
.setTitle('Felhasználó kitiltva')
.setDescription(`${member.user.tag} ki lett tiltva.`)
.setColor('Orange')
.setFooter({ text: '⛏️ by Laci 🛠️' });
interaction.client.logToGuildChannel(guildId, embed);
await interaction.reply({ embeds: [embed] });

// Moderation help embed
const helpEmbed = new EmbedBuilder()
.setTitle('🛠️ Nyx Moderation Commands')
.setDescription('Manage your server with style!')
.setColor(0xED4245)
.setThumbnail('https://cdn-icons-png.flaticon.com/512/1828/1828843.png')
.addFields(
{ name: '🔨 Ban', value: 'Ban a member from the server', inline: true },
{ name: '👢 Kick', value: 'Kick a member from the server', inline: true },
{ name: '🔇 Mute', value: 'Mute a member in the server', inline: true },
{ name: '⚠️ Warn', value: 'Warn a member', inline: true },
{ name: '🧹 Purge', value: 'Delete messages in bulk', inline: true },
{ name: '👥 KickRole', value: 'Kick all members with a specific role', inline: true }
)
.setFooter({ text: 'Use moderation commands responsibly!' })
.setTimestamp();
await interaction.reply({ embeds: [helpEmbed], flags: 64 });
},
};
130 changes: 130 additions & 0 deletions commands/moderation/deleterole.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
const { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js');
const { readUser, writeUser, appendUserLog } = require('../../../utils/jsondb');
const fs = require('fs');
const path = require('path');

module.exports = {
data: new SlashCommandBuilder()
.setName('deleterole')
.setDescription('Rang törlésének staff általi jóváhagyása')
.addUserOption(opt => opt.setName('user').setDescription('Felhasználó').setRequired(true))
.addRoleOption(opt => opt.setName('role').setDescription('Törlendő rang').setRequired(true))
.addStringOption(opt => opt.setName('reason').setDescription('Indok').setRequired(true)),
async execute(interaction) {
const user = interaction.options.getUser('user');
const role = interaction.options.getRole('role');
const reason = interaction.options.getString('reason');
const guildId = interaction.guild.id;
const config = await readUser('guilds', guildId, guildId);
const requestRoles = config.requestRoles || [];
if (requestRoles.length > 0 && !interaction.member.roles.cache.some(r => requestRoles.includes(r.id))) {
return interaction.reply({ content: 'Nincs jogosultságod rang törlésének kérelmezéséhez. Csak a kijelölt rang kérelmező rangok tagjai kérhetnek rang törlést.', ephemeral: true });
}
const staffRoles = config.staffRoles || (config.staffRole ? [config.staffRole] : []);
const deleteChannelId = config.deleteroleChannel;
const cooldownRoleId = config.roleCooldown;
if (!deleteChannelId || !staffRoles.length || !cooldownRoleId) {
return interaction.reply({ content: 'A deleterole channel, staff role vagy cooldown role nincs beállítva a szerver konfigurációban!', ephemeral: true });
}
const deleteChannel = await interaction.guild.channels.fetch(deleteChannelId).catch(() => null);
if (!deleteChannel) return interaction.reply({ content: 'A deleterole channel nem található!', ephemeral: true });
const embed = new EmbedBuilder()
.setTitle('🗑️ Rang törlés jóváhagyás')
.setDescription(`Felhasználó: <@${user.id}>\nTörlendő rang: <@&${role.id}>\nIndok: ${reason}`)
.addFields({ name: 'Kérelmezte', value: `<@${interaction.user.id}>`, inline: true })
.setColor('Orange')
.setFooter({ text: '⛏️ by Laci 🛠️' })
.setTimestamp();
const row = new ActionRowBuilder().addComponents(
new ButtonBuilder().setCustomId('deleterole_accept').setEmoji('✅').setStyle(ButtonStyle.Success),
new ButtonBuilder().setCustomId('deleterole_decline').setEmoji('❌').setStyle(ButtonStyle.Danger)
);
const msg = await deleteChannel.send({ embeds: [embed], components: [row] });
await interaction.reply({ content: 'A rang törlési kérelem elküldve a staff csatornába!', ephemeral: true });
const filter = i => i.member.roles.cache.some(r => staffRoles.includes(r.id)) && ['deleterole_accept','deleterole_decline'].includes(i.customId);
const collector = msg.createMessageComponentCollector({ filter, max: 1, time: 5 * 60 * 1000 });
collector.on('collect', async i => {
const reasonEmbed = new EmbedBuilder()
.setTitle(i.customId === 'deleterole_accept' ? '✅ Indok a rang törléséhez' : '❌ Indok az elutasításhoz')
.setDescription('Írd be az indokot erre a döntésre, csak te látod! (60mp)')
.setColor(i.customId === 'deleterole_accept' ? 'Green' : 'Red')
.setFooter({ text: `Staff: ${i.user.tag}` })
.setTimestamp();
await i.reply({ embeds: [reasonEmbed], ephemeral: true, fetchReply: true });
const msgFilter = m => m.author.id === i.user.id && m.channelId === i.channel.id;
const collected = await i.channel.awaitMessages({ filter: msgFilter, max: 1, time: 60000 });
if (collected.size > 0) {
const m = collected.first();
try { await m.delete(); } catch {}
if (i.customId === 'deleterole_accept') {
// Remove role from user, delete role, add cooldown role
const targetMember = await interaction.guild.members.fetch(user.id);
const cooldownRole = await interaction.guild.roles.fetch(cooldownRoleId).catch(() => null);
if (targetMember.roles.cache.has(role.id)) await targetMember.roles.remove(role);
let cooldownDays = 3;
// Kérjünk be cooldown időt is (3-7 nap)
await i.followUp({ content: 'Írd be hány napig legyen cooldown (3-7, alapértelmezett: 3):', ephemeral: true });
const cdFilter = m => m.author.id === i.user.id && m.channelId === i.channel.id && /^\d+$/.test(m.content.trim());
const cdCollected = await i.channel.awaitMessages({ filter: cdFilter, max: 1, time: 30000 });
if (cdCollected.size > 0) {
const cdMsg = cdCollected.first();
try { await cdMsg.delete(); } catch {}
const val = parseInt(cdMsg.content.trim(), 10);
if (val >= 3 && val <= 7) cooldownDays = val;
}
if (cooldownRole) await targetMember.roles.add(cooldownRole);
// Mentsük az időt az adatbázisba (pl. profiles)
const profile = await readUser('profiles', user.id, guildId);
profile.role_cooldown = {
role: cooldownRoleId,
until: Date.now() + cooldownDays * 24 * 60 * 60 * 1000
};
await writeUser('profiles', user.id, guildId, profile);
// NEM töröljük a rangot a szerverről, csak levesszük a felhasználóról
await msg.delete().catch(() => {});
const approveEmbed = new EmbedBuilder()
.setTitle('✅ Rang eltávolítva a felhasználóról')
.setDescription(`Felhasználó: <@${user.id}>\nEltávolított rang: <@&${role.id}>\nIndok: ${reason}`)
.addFields(
{ name: 'Staff indok', value: `${m.content}\n**Staff:** <@${i.user.id}>` },
{ name: 'Kérelmezte', value: `<@${interaction.user.id}>`, inline: true },
{ name: 'Cooldown idő', value: `${cooldownDays} nap`, inline: true }
)
.setColor('Green')
.setFooter({ text: `Elfogadta: ${i.user.tag}` })
.setTimestamp();
await deleteChannel.send({ embeds: [approveEmbed] });
} else {
await msg.edit({ embeds: [embed.setColor('Red').setFooter({ text: `Elutasította: ${i.user.tag} | Indok: ${m.content}` })], components: [] });
await msg.delete().catch(() => {});
const declineEmbed = new EmbedBuilder()
.setTitle('❌ Rang törlés elutasítva')
.setDescription(`Felhasználó: <@${user.id}>\nTörlendő rang: <@&${role.id}>\nIndok: ${reason}`)
.addFields(
{ name: 'Staff indok', value: `${m.content}\n**Staff:** <@${i.user.id}>` },
{ name: 'Kérelmezte', value: `<@${interaction.user.id}>`, inline: true }
)
.setColor('Red')
.setFooter({ text: `Elutasította: ${i.user.tag}` })
.setTimestamp();
await deleteChannel.send({ embeds: [declineEmbed] });
}
} else {
await i.followUp({ content: 'Nem érkezett indok, a rang nem került törlésre/elutasítva.', ephemeral: true });
}
});
// Jogosultság-ellenőrzés: csak staff vagy ManageRoles jogosultsággal
if (!interaction.member.permissions.has(PermissionFlagsBits.ManageRoles) && !interaction.member.permissions.has(PermissionFlagsBits.Administrator)) {
return interaction.reply({ content: 'Ehhez a parancshoz Manage Roles vagy Admin jogosultság szükséges!', ephemeral: true });
}
// Naplózás minden rangkérelemről saját jsondb-vel
await appendUserLog('role_requests', interaction.user.id, guildId, {
type: 'deleterole',
staff: interaction.user.id,
target: user.id,
role: role.id,
reason,
date: Date.now()
}, interaction.user.username);
}
};
Loading