Compare commits
9 Commits
d52431ffb5
...
6f24616c81
Author | SHA1 | Date | |
---|---|---|---|
6f24616c81 | |||
0f54fbf07e | |||
1e72ecdf53 | |||
e5644462b9 | |||
1b89c4440b | |||
cf968f198a | |||
b68cb0b219 | |||
0c1cc63b9b | |||
fbc9238e97 |
@ -1,59 +0,0 @@
|
|||||||
name: Build and Deploy
|
|
||||||
on: [push]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
Deploy Web:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
container:
|
|
||||||
image: catthehacker/ubuntu:act-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Use Nodejs
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 18
|
|
||||||
- run: npm install
|
|
||||||
working-directory: ./web
|
|
||||||
- run: npm run build
|
|
||||||
working-directory: ./web
|
|
||||||
|
|
||||||
- uses: docker/setup-qemu-action@v2
|
|
||||||
- uses: docker/setup-buildx-action@v2
|
|
||||||
|
|
||||||
- uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
registry: gitea.xeovalyte.dev
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
|
|
||||||
- run: docker buildx build -t gitea.xeovalyte.dev/xeovalyte/polarcraft-web:latest --load --platform=linux/amd64 ./web
|
|
||||||
- run: docker push gitea.xeovalyte.dev/xeovalyte/polarcraft-web:latest
|
|
||||||
|
|
||||||
Deploy Discord Bot:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
container:
|
|
||||||
image: catthehacker/ubuntu:act-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Use Nodejs
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 18
|
|
||||||
- run: npm install
|
|
||||||
working-directory: ./discord-bot
|
|
||||||
|
|
||||||
- uses: docker/setup-qemu-action@v2
|
|
||||||
- uses: docker/setup-buildx-action@v2
|
|
||||||
|
|
||||||
- uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
registry: gitea.xeovalyte.dev
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
|
|
||||||
- run: docker buildx build -t gitea.xeovalyte.dev/xeovalyte/polarcraft-discord:latest --load --platform=linux/amd64 ./discord-bot
|
|
||||||
- run: docker push gitea.xeovalyte.dev/xeovalyte/polarcraft-discord:latest
|
|
Before Width: | Height: | Size: 663 KiB |
Before Width: | Height: | Size: 601 KiB |
Before Width: | Height: | Size: 899 KiB |
BIN
assets/logo.png
Before Width: | Height: | Size: 984 KiB |
Before Width: | Height: | Size: 6.0 KiB |
2
discord-bot/.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
.env
|
|
||||||
node_modules
|
|
@ -1,11 +0,0 @@
|
|||||||
FROM node:18-alpine
|
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
EXPOSE 3000
|
|
||||||
|
|
||||||
CMD [ "node", "index.js" ]
|
|
@ -1,173 +0,0 @@
|
|||||||
const { SlashCommandBuilder } = require('discord.js');
|
|
||||||
const { QueryType } = require('discord-player');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName('music')
|
|
||||||
.setDescription('Play and configure music')
|
|
||||||
.addSubcommand(subcommand => subcommand
|
|
||||||
.setName('play')
|
|
||||||
.setDescription('Play music')
|
|
||||||
.addStringOption(option => option
|
|
||||||
.setName('link-or-query')
|
|
||||||
.setDescription('The song or link you want to play')
|
|
||||||
.setRequired(true)))
|
|
||||||
.addSubcommand(subcommand => subcommand
|
|
||||||
.setName('skip')
|
|
||||||
.setDescription('Skip current song'))
|
|
||||||
.addSubcommand(subcommand => subcommand
|
|
||||||
.setName('stop')
|
|
||||||
.setDescription('Stop current queue'))
|
|
||||||
.addSubcommand(subcommand => subcommand
|
|
||||||
.setName('pause')
|
|
||||||
.setDescription('Pause the current song'))
|
|
||||||
.addSubcommand(subcommand => subcommand
|
|
||||||
.setName('shuffle')
|
|
||||||
.setDescription('Shuffle the queue'))
|
|
||||||
.addSubcommand(subcommand => subcommand
|
|
||||||
.setName('resume')
|
|
||||||
.setDescription('Resume the current song'))
|
|
||||||
.addSubcommand(subcommand => subcommand
|
|
||||||
.setName('filter')
|
|
||||||
.setDescription('Apply a filter')
|
|
||||||
.addStringOption(option => option
|
|
||||||
.setName('type')
|
|
||||||
.setDescription('Type of filter')
|
|
||||||
.setRequired(true)
|
|
||||||
.addChoices(
|
|
||||||
{ name: 'Bassboost', value: 'bassboost_low' },
|
|
||||||
{ name: 'Nightcore', value: 'nightcore' },
|
|
||||||
)))
|
|
||||||
.addSubcommand(subcommand => subcommand
|
|
||||||
.setName('volume')
|
|
||||||
.setDescription('Set volume')
|
|
||||||
.addNumberOption(option => option
|
|
||||||
.setName('percentage')
|
|
||||||
.setDescription('The percentage of the volume between 1 and 100')
|
|
||||||
.setRequired(true))),
|
|
||||||
|
|
||||||
async execute({ interaction, createEmbed, client }) {
|
|
||||||
if (!interaction.member.voice.channelId) return await interaction.reply({ embeds: [createEmbed.basic('You are not in a voice channel!')], ephemeral: true });
|
|
||||||
if (interaction.guild.members.me.voice.channelId && interaction.member.voice.channelId !== interaction.guild.members.me.voice.channelId) {
|
|
||||||
return await interaction.reply({ embeds: [createEmbed.basic('You are not in my voice channel!')], ephemeral: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (interaction.options.getSubcommand() === 'play') {
|
|
||||||
const query = interaction.options.getString('link-or-query');
|
|
||||||
|
|
||||||
const queue = client.player.createQueue(interaction.guild, {
|
|
||||||
ytdlOptions: {
|
|
||||||
filter: 'audioonly',
|
|
||||||
highWaterMark: 1 << 30,
|
|
||||||
dlChunkSize: 0,
|
|
||||||
},
|
|
||||||
metadata: {
|
|
||||||
channel: interaction.channel,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// verify vc connection
|
|
||||||
try {
|
|
||||||
if (!queue.connection) await queue.connect(interaction.member.voice.channel);
|
|
||||||
} catch {
|
|
||||||
queue.destroy();
|
|
||||||
return await interaction.reply({ embeds: [createEmbed.basic('Could not join your voice channel!')], ephemeral: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
await interaction.reply({ embeds: [createEmbed.basic('Searching...')], ephemeral: true });
|
|
||||||
|
|
||||||
const searchResult = await client.player.search(query, {
|
|
||||||
requestedBy: interaction.user,
|
|
||||||
searchEngine: QueryType.AUTO,
|
|
||||||
});
|
|
||||||
if (!searchResult || !searchResult.tracks[0]) return await interaction.editReply({ embeds: [createEmbed.basic(`Track or playlist **${query}** not found!`)], ephemeral: true });
|
|
||||||
|
|
||||||
searchResult.playlist ? queue.addTracks(searchResult.tracks) : queue.addTrack(searchResult.tracks[0]);
|
|
||||||
if (!queue.playing) await queue.play();
|
|
||||||
|
|
||||||
if (!searchResult.playlist) await interaction.editReply({ embeds: [createEmbed.basic(`Adding track **${searchResult.tracks[0].title}**`)], ephemeral: false });
|
|
||||||
else await interaction.editReply({ embeds: [createEmbed.basic(`Adding **${searchResult.playlist.tracks.length}** tracks from playlist **${searchResult.playlist.title}**`)], ephemeral: false });
|
|
||||||
|
|
||||||
} else if (interaction.options.getSubcommand() === 'skip') {
|
|
||||||
|
|
||||||
const queue = client.player.getQueue(interaction.guild);
|
|
||||||
|
|
||||||
if (!queue || !queue.playing) return await interaction.reply({ embeds: [createEmbed.basic('No music is being played')] });
|
|
||||||
|
|
||||||
const currentTrack = queue.nowPlaying();
|
|
||||||
const success = queue.skip();
|
|
||||||
|
|
||||||
await interaction.reply({ embeds: [createEmbed.basic(success ? `Skipped **${currentTrack.title}**` : 'Something went wrong')] });
|
|
||||||
} else if (interaction.options.getSubcommand() === 'volume') {
|
|
||||||
const vol = interaction.options.getNumber('percentage');
|
|
||||||
|
|
||||||
const queue = client.player.getQueue(interaction.guild);
|
|
||||||
|
|
||||||
if (!queue || !queue.playing) return await interaction.reply({ embeds: [createEmbed.basic('No music is being played')] });
|
|
||||||
|
|
||||||
if (vol < 1 || vol > 100) return interaction.reply({ embeds: [createEmbed.basic('Volume must be between 1 and 100')] });
|
|
||||||
|
|
||||||
const success = queue.setVolume(vol);
|
|
||||||
|
|
||||||
await interaction.reply({ embeds: [createEmbed.basic(success ? `Volume set to **${vol}%**` : 'Something went wrong')] });
|
|
||||||
} else if (interaction.options.getSubcommand() === 'stop') {
|
|
||||||
|
|
||||||
const queue = client.player.getQueue(interaction.guild);
|
|
||||||
|
|
||||||
if (!queue || !queue.playing) return await interaction.reply({ embeds: [createEmbed.basic('No music is being played')] });
|
|
||||||
|
|
||||||
queue.destroy();
|
|
||||||
|
|
||||||
await interaction.reply({ embeds: [createEmbed.basic('Stopped the player')] });
|
|
||||||
} else if (interaction.options.getSubcommand() === 'pause') {
|
|
||||||
|
|
||||||
const queue = client.player.getQueue(interaction.guild);
|
|
||||||
|
|
||||||
if (!queue || !queue.playing) return await interaction.reply({ embeds: [createEmbed.basic('No music is being played')] });
|
|
||||||
|
|
||||||
queue.setPaused(true);
|
|
||||||
|
|
||||||
await interaction.reply({ embeds: [createEmbed.basic('Paused the player')] });
|
|
||||||
} else if (interaction.options.getSubcommand() === 'resume') {
|
|
||||||
|
|
||||||
const queue = client.player.getQueue(interaction.guild);
|
|
||||||
|
|
||||||
if (!queue || !queue.playing) return await interaction.reply({ embeds: [createEmbed.basic('No music is being played')] });
|
|
||||||
|
|
||||||
queue.setPaused(false);
|
|
||||||
|
|
||||||
await interaction.reply({ embeds: [createEmbed.basic('Resumed the player')] });
|
|
||||||
} else if (interaction.options.getSubcommand() === 'filter') {
|
|
||||||
const filter = interaction.options.getString('type');
|
|
||||||
|
|
||||||
const queue = client.player.getQueue(interaction.guild);
|
|
||||||
|
|
||||||
if (!queue || !queue.playing) return await interaction.reply({ embeds: [createEmbed.basic('No music is being played')] });
|
|
||||||
|
|
||||||
if (filter === 'bassboost_low') {
|
|
||||||
await queue.setFilters({
|
|
||||||
bassboost_low: !queue.getFiltersEnabled().includes('bassboost_low'),
|
|
||||||
normalizer2: !queue.getFiltersEnabled().includes('bassboost_low'),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filter === 'nightcore') {
|
|
||||||
await queue.setFilters({
|
|
||||||
nightcore: !queue.getFiltersEnabled().includes('nightcore'),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await interaction.reply({ embeds: [createEmbed.basic(queue.getFiltersEnabled().includes(filter) ? `Applied **${filter}** filter` : `Removed **${filter}** filter`)] });
|
|
||||||
} else if (interaction.options.getSubcommand() === 'shuffle') {
|
|
||||||
|
|
||||||
const queue = client.player.getQueue(interaction.guild);
|
|
||||||
|
|
||||||
if (!queue || !queue.playing) return await interaction.reply({ embeds: [createEmbed.basic('No music is being played')] });
|
|
||||||
|
|
||||||
const success = queue.shuffle();
|
|
||||||
|
|
||||||
await interaction.reply({ embeds: [createEmbed.basic(success ? 'The queue has been shuffled' : 'Something went wrong')] });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,12 +0,0 @@
|
|||||||
const { SlashCommandBuilder } = require('discord.js');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName('ping')
|
|
||||||
.setDescription('Replies with Pong!'),
|
|
||||||
|
|
||||||
async execute({ interaction, createEmbed, client }) {
|
|
||||||
const reply = await interaction.reply({ embeds: [createEmbed.basic(`Websocket heartbeat: **${client.ws.ping}ms**\n Roundtrip latency: **Pinging...**`)], fetchReply: true, ephemeral: true });
|
|
||||||
interaction.editReply({ embeds: [createEmbed.basic(`Websocket heartbeat: **${client.ws.ping}ms**\n Roundtrip latency: **${reply.createdTimestamp - interaction.createdTimestamp}ms**`)], emphemeral: true });
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,70 +0,0 @@
|
|||||||
const { SlashCommandBuilder, PermissionsBitField, ChannelType, ButtonStyle, ButtonBuilder, ActionRowBuilder } = require('discord.js');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
data: new SlashCommandBuilder()
|
|
||||||
.setName('ticket')
|
|
||||||
.setDescription('Commands for managing a ticket')
|
|
||||||
.addSubcommand(subcommand => subcommand
|
|
||||||
.setName('create')
|
|
||||||
.setDescription('Create a ticket with you and moderators'))
|
|
||||||
.addSubcommand(subcommand => subcommand
|
|
||||||
.setName('close')
|
|
||||||
.setDescription('Close current ticket')),
|
|
||||||
|
|
||||||
async execute({ interaction, createEmbed }) {
|
|
||||||
if (interaction.options.getSubcommand() === 'create') {
|
|
||||||
await interaction.reply({ embeds: [createEmbed.basic('Creating ticket channel...')], fetchReply: true, ephemeral: true });
|
|
||||||
|
|
||||||
const ticketChannel = await interaction.member.guild.channels.create({
|
|
||||||
name: `ticket-${interaction.user.username}`,
|
|
||||||
type: ChannelType.GuildText,
|
|
||||||
parent: process.env.TICKET_CATEGORY_ID,
|
|
||||||
permissionOverwrites: [
|
|
||||||
{
|
|
||||||
id: interaction.guild.id,
|
|
||||||
deny: [PermissionsBitField.Flags.ViewChannel],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: process.env.MODERATOR_ROLE_ID,
|
|
||||||
allow: [PermissionsBitField.Flags.ViewChannel],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: interaction.user.id,
|
|
||||||
allow: [PermissionsBitField.Flags.ViewChannel],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
await ticketChannel.send({ embeds: [createEmbed.basic(`${interaction.user} created a ticket`)] });
|
|
||||||
|
|
||||||
interaction.editReply({ embeds: [createEmbed.basic(`${ticketChannel} has been created`)], emphemeral: true });
|
|
||||||
|
|
||||||
} else if (interaction.options.getSubcommand() === 'close') {
|
|
||||||
|
|
||||||
if (!interaction.channel.name.startsWith('ticket')) return interaction.reply({ embeds: [createEmbed.basic('You must execute this command inside a ticket channel')], emphemeral: true });
|
|
||||||
|
|
||||||
const cancelRow = new ActionRowBuilder()
|
|
||||||
.addComponents(
|
|
||||||
new ButtonBuilder()
|
|
||||||
.setCustomId('cancel')
|
|
||||||
.setLabel('Cancel')
|
|
||||||
.setStyle(ButtonStyle.Danger),
|
|
||||||
);
|
|
||||||
|
|
||||||
const closeMessage = await interaction.reply({ embeds: [createEmbed.basic('Closing ticket in 10 seconds...')], components: [cancelRow] });
|
|
||||||
const collector = closeMessage.createMessageComponentCollector();
|
|
||||||
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
try {
|
|
||||||
interaction.channel.delete();
|
|
||||||
} catch {}
|
|
||||||
}, 10000);
|
|
||||||
|
|
||||||
collector.on('collect', async () => {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
await closeMessage.edit({ embeds: [createEmbed.basic('Ticket closing has been stopped')], components: [] });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,17 +0,0 @@
|
|||||||
const { Events } = require('discord.js');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
name: Events.MessageCreate,
|
|
||||||
async execute({ log }, message) {
|
|
||||||
|
|
||||||
if (message.channelId === process.env.MINECRAFT_CHANNEL_ID && !message.author.bot) {
|
|
||||||
await fetch(process.env.WEB_HOST + '/api/minecraft/message/chattominecraft', {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({
|
|
||||||
content: message.content,
|
|
||||||
discordId: message.author.id,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,17 +0,0 @@
|
|||||||
const { Events, EmbedBuilder } = require('discord.js');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
name: Events.GuildMemberAdd,
|
|
||||||
async execute({ client, log }, member) {
|
|
||||||
log.Info(`${member.user.username} has joined`);
|
|
||||||
|
|
||||||
const newMemberEmbed = new EmbedBuilder()
|
|
||||||
.setTitle(`${member.user.username} has joined!`)
|
|
||||||
.setDescription(`Welcome ${member} to the **Polarcraft** Discord server!`)
|
|
||||||
.setColor(process.env.EMBED_COLOR)
|
|
||||||
.setThumbnail(member.user.avatarURL());
|
|
||||||
|
|
||||||
const channel = await client.channels.cache.get(process.env.LOG_CHANNEL_ID);
|
|
||||||
channel.send({ embeds: [newMemberEmbed] });
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,15 +0,0 @@
|
|||||||
const { Events, EmbedBuilder } = require('discord.js');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
name: Events.GuildMemberRemove,
|
|
||||||
async execute({ client, log }, member) {
|
|
||||||
log.Info(`${member.user.username} has left`);
|
|
||||||
|
|
||||||
const newMemberEmbed = new EmbedBuilder()
|
|
||||||
.setTitle(`${member.user.username} has left!`)
|
|
||||||
.setColor(process.env.EMBED_COLOR);
|
|
||||||
|
|
||||||
const channel = await client.channels.cache.get(process.env.LOG_CHANNEL_ID);
|
|
||||||
channel.send({ embeds: [newMemberEmbed] });
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,26 +0,0 @@
|
|||||||
const { Events } = require('discord.js');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
name: Events.InteractionCreate,
|
|
||||||
async execute({ log, createEmbed, client }, interaction) {
|
|
||||||
|
|
||||||
if (interaction.isChatInputCommand()) {
|
|
||||||
const command = interaction.client.commands.get(interaction.commandName);
|
|
||||||
|
|
||||||
if (!command) {
|
|
||||||
return log.Error(`No command matching ${interaction.commandName} was found`);
|
|
||||||
} else {
|
|
||||||
log.Info(`${interaction.user.username} executed command ${interaction.commandName}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await command.execute({ interaction, log, createEmbed, client });
|
|
||||||
} catch (error) {
|
|
||||||
log.Error(error);
|
|
||||||
|
|
||||||
await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
};
|
|
@ -1,11 +0,0 @@
|
|||||||
const { Events } = require('discord.js');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
name: Events.ClientReady,
|
|
||||||
once: true,
|
|
||||||
execute({ client, log }) {
|
|
||||||
log.Info(`Ready! Logged in as ${client.user.tag}`);
|
|
||||||
|
|
||||||
require('../functions/registerCommands.js').registerCommands(log);
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,12 +0,0 @@
|
|||||||
const { EmbedBuilder } = require('discord.js');
|
|
||||||
const dotenv = require('dotenv');
|
|
||||||
|
|
||||||
dotenv.config();
|
|
||||||
|
|
||||||
const basic = (description) => {
|
|
||||||
return new EmbedBuilder()
|
|
||||||
.setColor(process.env.EMBED_COLOR)
|
|
||||||
.setDescription(description);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = { basic };
|
|
@ -1,31 +0,0 @@
|
|||||||
const { EmbedBuilder } = require('discord.js');
|
|
||||||
|
|
||||||
const registerEvents = ({ client }) => {
|
|
||||||
|
|
||||||
client.player.on('trackStart', (queue, track) => {
|
|
||||||
|
|
||||||
const nowPlayingEmbed = new EmbedBuilder()
|
|
||||||
.setTitle('Now Playing')
|
|
||||||
.setColor(process.env.EMBED_COLOR)
|
|
||||||
.setDescription(`[${track.title}](${track.url})`)
|
|
||||||
.addFields(
|
|
||||||
{ name: 'Requested By', value: `${track.requestedBy}`, inline: true },
|
|
||||||
{ name: 'Duration', value: track.duration, inline: true },
|
|
||||||
{ name: 'Queue', value: `${queue.tracks.length} song(s)`, inline: true },
|
|
||||||
{ name: 'Author', value: track.author, inline: true },
|
|
||||||
{ name: 'Source', value: track.source, inline: true },
|
|
||||||
{ name: 'Volume', value: `${queue.options.initialVolume}%`, inline: true },
|
|
||||||
{ name: 'Filters', value: queue.getFiltersEnabled().join('\n') || 'No filters active', inline: false },
|
|
||||||
)
|
|
||||||
.setThumbnail(track.thumbnail)
|
|
||||||
.setTimestamp();
|
|
||||||
|
|
||||||
queue.metadata.channel.send({ embeds: [nowPlayingEmbed] });
|
|
||||||
});
|
|
||||||
|
|
||||||
client.player.on('error', (queue, error) => {
|
|
||||||
console.log(error);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = { registerEvents };
|
|
@ -1,48 +0,0 @@
|
|||||||
const { REST, Routes } = require('discord.js');
|
|
||||||
const fs = require('node:fs');
|
|
||||||
const dotenv = require('dotenv');
|
|
||||||
|
|
||||||
dotenv.config();
|
|
||||||
|
|
||||||
const registerCommands = (log) => {
|
|
||||||
const commands = [];
|
|
||||||
// Grab all the command files from the commands directory you created earlier
|
|
||||||
const commandFiles = fs.readdirSync('./commands').filter(file => file.endsWith('.js'));
|
|
||||||
|
|
||||||
// Grab the SlashCommandBuilder#toJSON() output of each command's data for deployment
|
|
||||||
for (const file of commandFiles) {
|
|
||||||
const command = require(`../commands/${file}`);
|
|
||||||
commands.push(command.data.toJSON());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct and prepare an instance of the REST module
|
|
||||||
const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN);
|
|
||||||
|
|
||||||
// and deploy your commands!
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
log.Info(`Started refreshing ${commands.length} application (/) commands.`);
|
|
||||||
|
|
||||||
// The put method is used to fully refresh all commands in the guild with the current set
|
|
||||||
let data = null;
|
|
||||||
if (process.env.GUILD_ID !== 'production') {
|
|
||||||
data = await rest.put(
|
|
||||||
Routes.applicationGuildCommands(process.env.CLIENT_ID, process.env.GUILD_ID),
|
|
||||||
{ body: commands },
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
data = await rest.put(
|
|
||||||
Routes.applicationGuildCommands(process.env.CLIENT_ID),
|
|
||||||
{ body: commands },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info(`Successfully reloaded ${data.length} application (/) commands.`);
|
|
||||||
} catch (error) {
|
|
||||||
// And of course, make sure you catch and log any errors!
|
|
||||||
log.Error(error);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = { registerCommands };
|
|
3323
discord-bot/package-lock.json
generated
@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "commonjs",
|
|
||||||
"name": "discord-bot",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"lint": "eslint .",
|
|
||||||
"lint-fix": "eslint . --fix"
|
|
||||||
},
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"@discord-player/extractor": "^3.0.2",
|
|
||||||
"@discordjs/opus": "^0.8.0",
|
|
||||||
"chalk": "^4.1.2",
|
|
||||||
"discord-player": "^5.3.2",
|
|
||||||
"discord.js": "^14.7.1",
|
|
||||||
"distube": "^4.0.4",
|
|
||||||
"dotenv": "^16.0.3",
|
|
||||||
"eslint": "^8.29.0",
|
|
||||||
"express": "^4.18.2",
|
|
||||||
"ytdl-core": "^4.11.4"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
const { WebhookClient, EmbedBuilder } = require('discord.js');
|
|
||||||
const express = require('express');
|
|
||||||
|
|
||||||
const router = express.Router();
|
|
||||||
const webhookClient = new WebhookClient({ url: process.env.MINECRAFT_WEBHOOK_URL });
|
|
||||||
|
|
||||||
router.post('/sendchatmessage', (req, res) => {
|
|
||||||
const { content, username, avatarURL } = req.body;
|
|
||||||
|
|
||||||
if (!username || !content || !avatarURL) return res.status(400).send({ error: 'Content, username and avatar_url are required' });
|
|
||||||
|
|
||||||
webhookClient.send({
|
|
||||||
content: content,
|
|
||||||
username: username,
|
|
||||||
avatarURL: avatarURL,
|
|
||||||
});
|
|
||||||
|
|
||||||
res.send({ data: 'Test data' });
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post('/sendgamemessage', (req, res) => {
|
|
||||||
const { content, avatarURL } = req.body;
|
|
||||||
|
|
||||||
if (!content || !avatarURL) return res.status(400).send({ error: 'Content, username and avatar_url are required' });
|
|
||||||
|
|
||||||
const messageEmbed = new EmbedBuilder()
|
|
||||||
.setColor(process.env.EMBED_COLOR)
|
|
||||||
.setAuthor({ name: content, iconURL: avatarURL });
|
|
||||||
|
|
||||||
webhookClient.send({
|
|
||||||
embeds: [messageEmbed],
|
|
||||||
username: 'Server',
|
|
||||||
});
|
|
||||||
|
|
||||||
res.send({ data: 'Test data' });
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = router;
|
|
@ -1,145 +0,0 @@
|
|||||||
const express = require('express');
|
|
||||||
const index = require('../index');
|
|
||||||
const { PermissionsBitField, ChannelType } = require('discord.js');
|
|
||||||
|
|
||||||
const router = express.Router();
|
|
||||||
|
|
||||||
router.post('/createchannels', async (req, res) => {
|
|
||||||
const { name, discordId } = req.body;
|
|
||||||
|
|
||||||
if (!name || !discordId) return res.status(400).send({ error: 'Name en discordId zijn vereist' });
|
|
||||||
|
|
||||||
try {
|
|
||||||
const guild = await index.client.guilds.fetch(process.env.GUILD_ID);
|
|
||||||
|
|
||||||
const category = await guild.channels.fetch(process.env.TEAM_CATEGORY_ID);
|
|
||||||
|
|
||||||
const member = await guild.members.fetch(discordId);
|
|
||||||
|
|
||||||
const textChannel = await guild.channels.create({
|
|
||||||
name: name,
|
|
||||||
type: ChannelType.GuildText,
|
|
||||||
parent: category,
|
|
||||||
permissionOverwrites: [
|
|
||||||
{
|
|
||||||
id: guild.id,
|
|
||||||
deny: [PermissionsBitField.Flags.ViewChannel],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: member.id,
|
|
||||||
allow: [PermissionsBitField.Flags.ViewChannel],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const voiceChannel = await guild.channels.create({
|
|
||||||
name: name,
|
|
||||||
type: ChannelType.GuildVoice,
|
|
||||||
parent: category,
|
|
||||||
permissionOverwrites: [
|
|
||||||
{
|
|
||||||
id: guild.id,
|
|
||||||
deny: [PermissionsBitField.Flags.ViewChannel],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: member.id,
|
|
||||||
allow: [PermissionsBitField.Flags.ViewChannel],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
res.send({ textChannel, voiceChannel });
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
return res.status(500).send({ error: 'Error tijdens het maken van discord channels' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
router.post('/deletechannels', async (req, res) => {
|
|
||||||
const { textChannelId, voiceChannelId } = req.body;
|
|
||||||
|
|
||||||
if (!textChannelId, !voiceChannelId) return res.status(400).send({ error: 'textChannelId en voiceChannelId zijn vereist' });
|
|
||||||
|
|
||||||
try {
|
|
||||||
const guild = await index.client.guilds.fetch(process.env.GUILD_ID);
|
|
||||||
|
|
||||||
const textChannel = await guild.channels.fetch(textChannelId);
|
|
||||||
const voiceChannel = await guild.channels.fetch(voiceChannelId);
|
|
||||||
|
|
||||||
await textChannel.delete();
|
|
||||||
await voiceChannel.delete();
|
|
||||||
|
|
||||||
res.send({ status: 'success' });
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
return res.status(500).send({ error: 'Error tijdens het verwijderen van discord channels' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post('/removeteammember', async (req, res) => {
|
|
||||||
const { textChannelId, voiceChannelId, discordId } = req.body;
|
|
||||||
|
|
||||||
if (!textChannelId, !voiceChannelId, !discordId) return res.status(400).send({ error: 'textChannelId, voiceChannelId en discordId zijn vereist' });
|
|
||||||
|
|
||||||
try {
|
|
||||||
const guild = await index.client.guilds.fetch(process.env.GUILD_ID);
|
|
||||||
const member = await guild.members.fetch(discordId);
|
|
||||||
|
|
||||||
const textChannel = await guild.channels.fetch(textChannelId);
|
|
||||||
const voiceChannel = await guild.channels.fetch(voiceChannelId);
|
|
||||||
|
|
||||||
await textChannel.permissionOverwrites.delete(member);
|
|
||||||
await voiceChannel.permissionOverwrites.delete(member);
|
|
||||||
|
|
||||||
res.send({ status: 'success' });
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
return res.status(500).send({ error: 'Error tijdens het verwijderen van een team member' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post('/addteammember', async (req, res) => {
|
|
||||||
const { textChannelId, voiceChannelId, discordId } = req.body;
|
|
||||||
|
|
||||||
if (!textChannelId, !voiceChannelId, !discordId) return res.status(400).send({ error: 'textChannelId, voiceChannelId en discordId zijn vereist' });
|
|
||||||
|
|
||||||
try {
|
|
||||||
const guild = await index.client.guilds.fetch(process.env.GUILD_ID);
|
|
||||||
const member = await guild.members.fetch(discordId);
|
|
||||||
|
|
||||||
const textChannel = await guild.channels.fetch(textChannelId);
|
|
||||||
const voiceChannel = await guild.channels.fetch(voiceChannelId);
|
|
||||||
|
|
||||||
await textChannel.permissionOverwrites.edit(member, { ViewChannel: true });
|
|
||||||
await voiceChannel.permissionOverwrites.edit(member, { ViewChannel: true });
|
|
||||||
|
|
||||||
res.send({ status: 'success' });
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
return res.status(500).send({ error: 'Error tijdens het toevoegen van team member' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post('/edit', async (req, res) => {
|
|
||||||
const { textChannelId, voiceChannelId, name } = req.body;
|
|
||||||
|
|
||||||
if (!textChannelId, !voiceChannelId, !name) return res.status(400).send({ error: 'textChannelId, voiceChannelId en name zijn vereist' });
|
|
||||||
|
|
||||||
try {
|
|
||||||
const guild = await index.client.guilds.fetch(process.env.GUILD_ID);
|
|
||||||
|
|
||||||
const textChannel = await guild.channels.fetch(textChannelId);
|
|
||||||
const voiceChannel = await guild.channels.fetch(voiceChannelId);
|
|
||||||
|
|
||||||
await textChannel.edit({ name: name });
|
|
||||||
await voiceChannel.edit({ name: name });
|
|
||||||
|
|
||||||
res.send({ status: 'success' });
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
return res.status(500).send({ error: 'Error tijds het veranderen van Discord channel naam' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = router;
|
|
@ -1,47 +0,0 @@
|
|||||||
const express = require('express');
|
|
||||||
const index = require('../index');
|
|
||||||
|
|
||||||
const router = express.Router();
|
|
||||||
|
|
||||||
router.post('/changenickname', async (req, res) => {
|
|
||||||
const { nickname, discordId } = req.body;
|
|
||||||
|
|
||||||
if (!nickname || !discordId) return res.status(400).send({ error: 'Nickname en discordId zijn vereist' });
|
|
||||||
|
|
||||||
const nick = nickname.length > 32 ? nickname.slice(0, 32) : nickname;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const guild = await index.client.guilds.fetch(process.env.GUILD_ID);
|
|
||||||
|
|
||||||
const member = await guild.members.fetch(discordId);
|
|
||||||
|
|
||||||
await member.edit({ nick: nick });
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
return res.status(500).send({ error: 'Error tijds het veranderen van de nickname' });
|
|
||||||
}
|
|
||||||
|
|
||||||
res.send({ status: 'success' });
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post('/ban', async (req, res) => {
|
|
||||||
const { discordId, reason } = req.body;
|
|
||||||
|
|
||||||
if (!reason || !discordId) return res.status(400).send({ error: 'Reason en discordId zijn vereist' });
|
|
||||||
|
|
||||||
try {
|
|
||||||
const guild = await index.client.guilds.fetch(process.env.GUILD_ID);
|
|
||||||
|
|
||||||
const member = await guild.members.fetch(discordId);
|
|
||||||
|
|
||||||
await member.ban({ deleteMessageSeconds: 24 * 3600, reason: reason });
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
return res.status(500).send({ error: 'Error tijds het bannen van gebruiker' });
|
|
||||||
}
|
|
||||||
|
|
||||||
res.send({ status: 'success' });
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = router;
|
|
@ -5,7 +5,8 @@
|
|||||||
"es6": true
|
"es6": true
|
||||||
},
|
},
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": 2021
|
"ecmaVersion": 2021,
|
||||||
|
"sourceType": "module"
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"arrow-spacing": ["warn", { "before": true, "after": true }],
|
"arrow-spacing": ["warn", { "before": true, "after": true }],
|
29
discordbot/commands/adduser.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
const { SlashCommandBuilder } = require('discord.js');
|
||||||
|
const { simpleEmbed } = require('../functions/embeds.js');
|
||||||
|
const { Users } = require('../functions/models.js');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('adduser')
|
||||||
|
.setDescription('Add a user to the system')
|
||||||
|
.addUserOption(option => option
|
||||||
|
.setName('user')
|
||||||
|
.setDescription('The user to add')),
|
||||||
|
|
||||||
|
async execute(interaction) {
|
||||||
|
const user = interaction.options.getUser('user') ? interaction.options.getUser('user') : interaction.user;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Users.create({
|
||||||
|
id: user.id,
|
||||||
|
rawUsername: user.globalName,
|
||||||
|
});
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [simpleEmbed('Added user to the system')], ephemeral: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [simpleEmbed('There was an error while adding the user')], ephemeral: true });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
49
discordbot/commands/ban.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
const { SlashCommandBuilder } = require('discord.js');
|
||||||
|
const { simpleEmbed } = require('../functions/embeds.js');
|
||||||
|
const { Users } = require('../functions/models.js');
|
||||||
|
const { client } = require('../index.js');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('ban')
|
||||||
|
.setDescription('Ban a user')
|
||||||
|
.setDefaultMemberPermissions(0)
|
||||||
|
.addUserOption(option => option
|
||||||
|
.setName('user')
|
||||||
|
.setDescription('The user to ban')
|
||||||
|
.setRequired(true))
|
||||||
|
.addStringOption(option => option
|
||||||
|
.setName('reason')
|
||||||
|
.setDescription('Why ban the user')
|
||||||
|
.setRequired(true)),
|
||||||
|
|
||||||
|
async execute(interaction) {
|
||||||
|
const member = interaction.options.getMember('user');
|
||||||
|
const reason = interaction.options.getString('reason');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await member.ban({ reason });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return await interaction.reply({ embeds: [simpleEmbed(`Error while banning ${member}`)], ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const userInstance = await Users.findOne({ where: { id: member.id } });
|
||||||
|
if (!userInstance) return await interaction.reply({ embeds: [simpleEmbed('Error while getting user information')], ephemeral: true });
|
||||||
|
|
||||||
|
if (userInstance.minecraftUuid) {
|
||||||
|
await fetch(process.env.MINECRAFT_HOST + '/console', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'content-type': 'text/plain',
|
||||||
|
},
|
||||||
|
body: `ban ${userInstance.minecraftUuid} ${reason}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const channel = client.channels.cache.get(process.env.MOD_LOG_CHANNEL_ID);
|
||||||
|
await channel.send({ embeds: [simpleEmbed(`${interaction.user} banned ${member}, reason: **${reason}**`)] });
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [simpleEmbed(`Banned ${member}`)], ephemeral: true });
|
||||||
|
},
|
||||||
|
};
|
@ -1,4 +1,5 @@
|
|||||||
const { SlashCommandBuilder, PermissionFlagsBits } = require('discord.js');
|
const { SlashCommandBuilder, PermissionFlagsBits } = require('discord.js');
|
||||||
|
const { simpleEmbed } = require('../functions/embeds.js');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
@ -10,15 +11,13 @@ module.exports = {
|
|||||||
.setDescription('The amount of messages to clear')
|
.setDescription('The amount of messages to clear')
|
||||||
.setRequired(true)),
|
.setRequired(true)),
|
||||||
|
|
||||||
async execute({ interaction, createEmbed, client }) {
|
async execute(interaction) {
|
||||||
const amount = interaction.options.getNumber('amount');
|
const amount = interaction.options.getNumber('amount');
|
||||||
|
|
||||||
if (amount < 1 || amount > 100) return await interaction.reply({ embeds: [createEmbed.basic('The amount must be between 1-100')] });
|
if (amount < 1 || amount > 100) return await interaction.reply({ embeds: [simpleEmbed('The amount must be between 1-100')] });
|
||||||
|
|
||||||
const channel = client.channels.cache.get(interaction.channelId);
|
interaction.channel.bulkDelete(amount, true);
|
||||||
|
|
||||||
channel.bulkDelete(amount);
|
await interaction.reply({ embeds: [simpleEmbed(`Cleared **${amount}** messages`)], ephemeral: true });
|
||||||
|
|
||||||
await interaction.reply({ embeds: [createEmbed.basic(`Cleared **${amount}** messages`)], ephemeral: true });
|
|
||||||
},
|
},
|
||||||
};
|
};
|
11
discordbot/commands/dennis.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
const { SlashCommandBuilder } = require('discord.js');
|
||||||
|
const { simpleEmbed } = require('../functions/embeds.js');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('dennis')
|
||||||
|
.setDescription('Dennis.'),
|
||||||
|
async execute(interaction) {
|
||||||
|
await interaction.reply({ embeds: [simpleEmbed('Dennis is koel')], ephemeral: true });
|
||||||
|
},
|
||||||
|
};
|
14
discordbot/commands/ping.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
const { SlashCommandBuilder } = require('discord.js');
|
||||||
|
const { simpleEmbed } = require('../functions/embeds.js');
|
||||||
|
const { client } = require('../index.js');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('ping')
|
||||||
|
.setDescription('Replies with Pong!'),
|
||||||
|
async execute(interaction) {
|
||||||
|
const reply = await interaction.reply({ embeds: [simpleEmbed(`Websocket heartbeat: **${client.ws.ping}ms**\n Roundtrip latency: **Pinging...**`)], fetchReply: true, emphemeral: true });
|
||||||
|
|
||||||
|
interaction.editReply({ embeds: [simpleEmbed(`Websocket heartbeat: **${client.ws.ping}ms**\n Roundtrip latency: **${reply.createdTimestamp - interaction.createdTimestamp}ms**`)] });
|
||||||
|
},
|
||||||
|
};
|
31
discordbot/commands/removewhitelist.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
const { SlashCommandBuilder } = require('discord.js');
|
||||||
|
const { simpleEmbed } = require('../functions/embeds.js');
|
||||||
|
const { Minecraft, Users } = require('../functions/models.js');
|
||||||
|
const { applyUsername } = require('../functions/utils.js');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('removewhitelist')
|
||||||
|
.setDescription('Remove yourself from the whitelist'),
|
||||||
|
|
||||||
|
async execute(interaction) {
|
||||||
|
try {
|
||||||
|
const user = await Users.findOne({ where: { id: interaction.user.id } });
|
||||||
|
|
||||||
|
if (!user.minecraftUuid) return await interaction.reply({ embeds: [simpleEmbed('You are not whitelisted')], ephemeral: true });
|
||||||
|
|
||||||
|
await Minecraft.destroy({ where: { uuid: user.minecraftUuid } });
|
||||||
|
|
||||||
|
await applyUsername(user, interaction.member);
|
||||||
|
|
||||||
|
const role = await interaction.guild.roles.fetch(process.env.MINECRAFT_ROLE_ID);
|
||||||
|
await interaction.member.roles.remove(role);
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [simpleEmbed('Successfully removed you from the whitelist')], ephemeral: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [simpleEmbed('There was an error while removing you from the whitelist')], ephemeral: true });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
38
discordbot/commands/setusername.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
const { SlashCommandBuilder } = require('discord.js');
|
||||||
|
const { simpleEmbed } = require('../functions/embeds.js');
|
||||||
|
const { Users } = require('../functions/models.js');
|
||||||
|
const { applyUsername } = require('../functions/utils.js');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('setusername')
|
||||||
|
.setDescription('Choose between Discord or Minecraft username')
|
||||||
|
.addStringOption(option => option
|
||||||
|
.setName('type')
|
||||||
|
.setDescription('Discord or Minecraft')
|
||||||
|
.setRequired(true)
|
||||||
|
.addChoices(
|
||||||
|
{ name: 'Discord', value: 'discord' },
|
||||||
|
{ name: 'Minecraft', value: 'minecraft' },
|
||||||
|
)),
|
||||||
|
|
||||||
|
async execute(interaction) {
|
||||||
|
const usernameType = interaction.options.getString('type');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const user = await Users.findOne({ where: { id: interaction.user.id } });
|
||||||
|
|
||||||
|
user.useMinecraftUsername = usernameType === 'minecraft' ? true : false;
|
||||||
|
|
||||||
|
await user.save();
|
||||||
|
|
||||||
|
await applyUsername(user, interaction.member);
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [simpleEmbed('Successfully changed your username type')], ephemeral: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [simpleEmbed('There was an error while changing your username type')], ephemeral: true });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
49
discordbot/commands/suspend.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
const { SlashCommandBuilder } = require('discord.js');
|
||||||
|
const { simpleEmbed } = require('../functions/embeds.js');
|
||||||
|
const { Users } = require('../functions/models.js');
|
||||||
|
const { client } = require('../index.js');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('suspend')
|
||||||
|
.setDescription('Suspend a user')
|
||||||
|
.setDefaultMemberPermissions(0)
|
||||||
|
.addUserOption(option => option
|
||||||
|
.setName('user')
|
||||||
|
.setDescription('The user to suspend')
|
||||||
|
.setRequired(true))
|
||||||
|
.addNumberOption(option => option
|
||||||
|
.setName('time')
|
||||||
|
.setDescription('The amount of time to suspend the user in minutes')
|
||||||
|
.setRequired(true))
|
||||||
|
.addStringOption(option => option
|
||||||
|
.setName('reason')
|
||||||
|
.setDescription('Why suspend the user')
|
||||||
|
.setRequired(true)),
|
||||||
|
|
||||||
|
async execute(interaction) {
|
||||||
|
const time = interaction.options.getNumber('time');
|
||||||
|
const member = interaction.options.getMember('user');
|
||||||
|
const reason = interaction.options.getString('reason');
|
||||||
|
|
||||||
|
await member.timeout(time * 60 * 1000, reason);
|
||||||
|
|
||||||
|
const user = await Users.findOne({ where: { id: member.id } });
|
||||||
|
if (!user) return await interaction.reply({ embeds: [simpleEmbed('Error while getting user information')], ephemeral: true });
|
||||||
|
|
||||||
|
if (user.minecraftUuid) {
|
||||||
|
await fetch(process.env.MINECRAFT_HOST + '/console', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'content-type': 'text/plain',
|
||||||
|
},
|
||||||
|
body: `tempban ${user.minecraftUuid} ${time}m ${reason}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const channel = client.channels.cache.get(process.env.MOD_LOG_CHANNEL_ID);
|
||||||
|
await channel.send({ embeds: [simpleEmbed(`${interaction.user} suspended ${member} for **${time}** minutes, reason: **${reason}**`)] });
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [simpleEmbed(`Suspended ${member} for **${time}** minutes`)], ephemeral: true });
|
||||||
|
},
|
||||||
|
};
|
208
discordbot/commands/team.js
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
const { SlashCommandBuilder, ModalBuilder, TextInputStyle, TextInputBuilder, ActionRowBuilder, ChannelType, PermissionsBitField, ButtonBuilder, ButtonStyle, ComponentType } = require('discord.js');
|
||||||
|
const { simpleEmbed } = require('../functions/embeds.js');
|
||||||
|
const { Users, Team } = require('../functions/models.js');
|
||||||
|
const { applyUsername } = require('../functions/utils.js');
|
||||||
|
const { client } = require('../index.js');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('team')
|
||||||
|
.setDescription('Team command')
|
||||||
|
.addSubcommand(subcommand => subcommand
|
||||||
|
.setName('create')
|
||||||
|
.setDescription('Create a team'))
|
||||||
|
.addSubcommand(subcommand => subcommand
|
||||||
|
.setName('invite')
|
||||||
|
.setDescription('Invite an user')
|
||||||
|
.addUserOption(option => option
|
||||||
|
.setName('user')
|
||||||
|
.setDescription('The user to invite')
|
||||||
|
.setRequired(true)))
|
||||||
|
.addSubcommand(subcommand => subcommand
|
||||||
|
.setName('leave')
|
||||||
|
.setDescription('Leave your team')),
|
||||||
|
|
||||||
|
async execute(interaction) {
|
||||||
|
const user = await Users.findOne({ where: { id: interaction.user.id } });
|
||||||
|
if (!user) return await interaction.reply({ embeds: [simpleEmbed('Error while getting user information')], ephemeral: true });
|
||||||
|
|
||||||
|
if (interaction.options.getSubcommand() === 'create') {
|
||||||
|
if (user.teamId) return await interaction.reply({ embeds: [simpleEmbed('You are already in a team')], ephemeral: true });
|
||||||
|
|
||||||
|
const modal = new ModalBuilder()
|
||||||
|
.setCustomId('teamCreate')
|
||||||
|
.setTitle('Create team');
|
||||||
|
|
||||||
|
const nameInput = new TextInputBuilder()
|
||||||
|
.setCustomId('nameInput')
|
||||||
|
.setLabel('Team name')
|
||||||
|
.setMinLength(3)
|
||||||
|
.setMaxLength(16)
|
||||||
|
.setStyle(TextInputStyle.Short);
|
||||||
|
|
||||||
|
const colorInput = new TextInputBuilder()
|
||||||
|
.setCustomId('colorInput')
|
||||||
|
.setLabel('Team color (hex)')
|
||||||
|
.setStyle(TextInputStyle.Short)
|
||||||
|
.setPlaceholder('#00ff00');
|
||||||
|
|
||||||
|
const firstActionRow = new ActionRowBuilder().addComponents(nameInput);
|
||||||
|
const secondActionRow = new ActionRowBuilder().addComponents(colorInput);
|
||||||
|
|
||||||
|
modal.addComponents(firstActionRow, secondActionRow);
|
||||||
|
|
||||||
|
await interaction.showModal(modal);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const filter = (filterInteraction) => filterInteraction.customId === 'teamCreate';
|
||||||
|
const modalInteraction = await interaction.awaitModalSubmit({ filter, time: 10 * 60 * 1000 });
|
||||||
|
|
||||||
|
const teamName = modalInteraction.fields.getTextInputValue('nameInput');
|
||||||
|
const teamColor = modalInteraction.fields.getTextInputValue('colorInput');
|
||||||
|
|
||||||
|
if (!/^[a-zA-Z0-9]+$/.test(teamName)) return await modalInteraction.reply({ embeds: [simpleEmbed('Team name can only include alphanumeric characters')], ephemeral: true });
|
||||||
|
if (!/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/.test(teamColor)) return await modalInteraction.reply({ embeds: [simpleEmbed('Team color must be a valid hex code. [Color Picker](https://htmlcolorcodes.com/color-picker/)')], ephemeral: true });
|
||||||
|
|
||||||
|
|
||||||
|
const guild = await client.guilds.fetch(process.env.GUILD_ID);
|
||||||
|
const category = await guild.channels.fetch(process.env.TEAM_CATEGORY_ID);
|
||||||
|
|
||||||
|
const textChannel = await guild.channels.create({
|
||||||
|
name: teamName,
|
||||||
|
type: ChannelType.GuildText,
|
||||||
|
parent: category,
|
||||||
|
permissionOverwrites: [
|
||||||
|
{
|
||||||
|
id: guild.id,
|
||||||
|
deny: [PermissionsBitField.Flags.ViewChannel],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: interaction.member.id,
|
||||||
|
allow: [PermissionsBitField.Flags.ViewChannel],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const voiceChannel = await guild.channels.create({
|
||||||
|
name: teamName,
|
||||||
|
type: ChannelType.GuildVoice,
|
||||||
|
parent: category,
|
||||||
|
permissionOverwrites: [
|
||||||
|
{
|
||||||
|
id: guild.id,
|
||||||
|
deny: [PermissionsBitField.Flags.ViewChannel],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: interaction.member.id,
|
||||||
|
allow: [PermissionsBitField.Flags.ViewChannel],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const team = await Team.create({
|
||||||
|
name: teamName,
|
||||||
|
color: teamColor,
|
||||||
|
textChannelId: textChannel.id,
|
||||||
|
voiceChannelId: voiceChannel.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await team.addMembers([ user ]);
|
||||||
|
|
||||||
|
await applyUsername(user, interaction.member);
|
||||||
|
|
||||||
|
await modalInteraction.reply({ embeds: [simpleEmbed(`Successfully created team **${teamName}**`)], ephemeral: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
} else if (interaction.options.getSubcommand() === 'leave') {
|
||||||
|
if (!user.teamId) return await interaction.reply({ embeds: [simpleEmbed('You are not in a team')], ephemeral: true });
|
||||||
|
|
||||||
|
const team = await user.getTeam();
|
||||||
|
await team.removeMember(user);
|
||||||
|
|
||||||
|
const guild = await client.guilds.fetch(process.env.GUILD_ID);
|
||||||
|
|
||||||
|
const textChannel = await guild.channels.fetch(team.textChannelId);
|
||||||
|
const voiceChannel = await guild.channels.fetch(team.voiceChannelId);
|
||||||
|
|
||||||
|
|
||||||
|
if (await team.countMembers() <= 0) {
|
||||||
|
await textChannel.delete();
|
||||||
|
await voiceChannel.delete();
|
||||||
|
|
||||||
|
await team.destroy();
|
||||||
|
} else {
|
||||||
|
await textChannel.permissionOverwrites.delete(interaction.member);
|
||||||
|
await voiceChannel.permissionOverwrites.delete(interaction.member);
|
||||||
|
}
|
||||||
|
|
||||||
|
await applyUsername(user, interaction.member);
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [simpleEmbed('Successfully left team')], ephemeral: true });
|
||||||
|
} else if (interaction.options.getSubcommand() === 'invite') {
|
||||||
|
if (!user.teamId) return await interaction.reply({ embeds: [simpleEmbed('You are not in a team')], ephemeral: true });
|
||||||
|
|
||||||
|
const invitedUser = interaction.options.getUser('user');
|
||||||
|
if (interaction.user.id === invitedUser.id) return await interaction.reply({ embeds: [simpleEmbed('You cannot invite yourself')], ephemeral: true });
|
||||||
|
|
||||||
|
const invitedUserInstance = await Users.findOne({ where: { id: invitedUser.id } });
|
||||||
|
if (invitedUserInstance.teamId) return await interaction.reply({ embeds: [simpleEmbed('The user you are trying already is in a team')], ephemeral: true });
|
||||||
|
|
||||||
|
const team = await user.getTeam();
|
||||||
|
|
||||||
|
const acceptButton = new ButtonBuilder()
|
||||||
|
.setCustomId('accept')
|
||||||
|
.setLabel('Accept')
|
||||||
|
.setStyle(ButtonStyle.Success);
|
||||||
|
|
||||||
|
const denyButton = new ButtonBuilder()
|
||||||
|
.setCustomId('deny')
|
||||||
|
.setLabel('Deny')
|
||||||
|
.setStyle(ButtonStyle.Danger);
|
||||||
|
|
||||||
|
const row = new ActionRowBuilder()
|
||||||
|
.addComponents(acceptButton, denyButton);
|
||||||
|
|
||||||
|
const inviteMessage = await invitedUser.send({
|
||||||
|
embeds: [simpleEmbed(`**${user.rawUsername}** invited you to join team **${team.name}**`)],
|
||||||
|
components: [row],
|
||||||
|
});
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [simpleEmbed(`Successfully invited ${invitedUser} to your team`)], ephemeral: true });
|
||||||
|
|
||||||
|
const collector = inviteMessage.createMessageComponentCollector({ componentType: ComponentType.Button, time: 60 * 60 * 1000 });
|
||||||
|
|
||||||
|
collector.on('collect', async i => {
|
||||||
|
try {
|
||||||
|
if (i.customId === 'accept') {
|
||||||
|
await invitedUserInstance.reload();
|
||||||
|
if (invitedUserInstance.teamId) return await i.message.edit({ embeds: [simpleEmbed('You are already in a team')], components: [] });
|
||||||
|
|
||||||
|
await team.addMember(i.user.id);
|
||||||
|
|
||||||
|
const guild = await client.guilds.fetch(process.env.GUILD_ID);
|
||||||
|
const member = await guild.members.fetch(i.user.id);
|
||||||
|
|
||||||
|
const textChannel = await guild.channels.fetch(team.textChannelId);
|
||||||
|
const voiceChannel = await guild.channels.fetch(team.voiceChannelId);
|
||||||
|
|
||||||
|
await textChannel.permissionOverwrites.edit(member, { ViewChannel: true });
|
||||||
|
await voiceChannel.permissionOverwrites.edit(member, { ViewChannel: true });
|
||||||
|
|
||||||
|
await applyUsername(invitedUserInstance, member);
|
||||||
|
|
||||||
|
await i.message.edit({ embeds: [simpleEmbed(`Successfully joined team **${team.name}**`)], components: [] });
|
||||||
|
} else if (i.customId === 'deny') {
|
||||||
|
await i.message.edit({ embeds: [simpleEmbed(`Successfully denied the request to join team **${team.name}**`)], components: [] });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
collector.on('end', async () => {
|
||||||
|
inviteMessage.edit({ embeds: [simpleEmbed('Confirmation not received within 1 hour, cancelling')], components: [] });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
51
discordbot/commands/whitelist.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
const { SlashCommandBuilder } = require('discord.js');
|
||||||
|
const { simpleEmbed } = require('../functions/embeds.js');
|
||||||
|
const { Minecraft, Users } = require('../functions/models.js');
|
||||||
|
const { applyUsername } = require('../functions/utils.js');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('whitelist')
|
||||||
|
.setDescription('Whitelist yourself on the Minecraft server')
|
||||||
|
.addStringOption(option => option
|
||||||
|
.setName('code')
|
||||||
|
.setDescription('6 digit code from Minecraft')
|
||||||
|
.setRequired(true)),
|
||||||
|
|
||||||
|
async execute(interaction) {
|
||||||
|
const code = interaction.options.getString('code');
|
||||||
|
|
||||||
|
if (code.length !== 6) return await interaction.reply({ embeds: [simpleEmbed('The code must be 6 digits long')], ephemeral: true });
|
||||||
|
const user = await Users.findOne({ where: { id: interaction.user.id } });
|
||||||
|
|
||||||
|
if (!user) return await interaction.reply({ embeds: [simpleEmbed('There was an error while finding the user')], ephemeral: true });
|
||||||
|
if (user.minecraftUuid) return await interaction.reply({ embeds: [simpleEmbed('You are already whitelisted')], ephemeral: true });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const minecraftCol = await Minecraft.findOne({ where: { code: code } });
|
||||||
|
|
||||||
|
if (!minecraftCol) return await interaction.reply({ embeds: [simpleEmbed('The code was not linked with a Minecraft account')], ephemeral: true });
|
||||||
|
|
||||||
|
if (minecraftCol.whitelisted) return await interaction.reply({ embeds: [simpleEmbed('Minecraft account already whitelisted')], ephemeral: true });
|
||||||
|
|
||||||
|
|
||||||
|
minecraftCol.whitelisted = true;
|
||||||
|
delete minecraftCol.code;
|
||||||
|
|
||||||
|
user.minecraftUuid = minecraftCol.uuid;
|
||||||
|
|
||||||
|
await minecraftCol.save();
|
||||||
|
await user.save();
|
||||||
|
|
||||||
|
await applyUsername(user, interaction.member);
|
||||||
|
|
||||||
|
const role = await interaction.guild.roles.fetch(process.env.MINECRAFT_ROLE_ID);
|
||||||
|
await interaction.member.roles.add(role);
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [simpleEmbed('You are successfully whitelisted')], ephemeral: true });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
};
|
BIN
discordbot/database.sqlite
Normal file
26
discordbot/events/guildMemberAdd.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
const { Events, EmbedBuilder } = require('discord.js');
|
||||||
|
const { client } = require('../index.js');
|
||||||
|
const { Users } = require('../functions/models');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: Events.GuildMemberAdd,
|
||||||
|
async execute(member) {
|
||||||
|
const addMemberEmbed = new EmbedBuilder()
|
||||||
|
.setTitle(`${member.user.globalName} has joined!`)
|
||||||
|
.setDescription(`Welcome ${member} to the **Polarcraft** Discord server!`)
|
||||||
|
.setColor(process.env.EMBED_COLOR)
|
||||||
|
.setThumbnail(member.user.avatarURL());
|
||||||
|
|
||||||
|
const channel = client.channels.cache.get(process.env.LOG_CHANNEL_ID);
|
||||||
|
await channel.send({ embeds: [addMemberEmbed] });
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Users.create({
|
||||||
|
id: member.user.id,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (error.name === 'SequelizeUniqueConstraintError') return;
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
14
discordbot/events/guildMemberRemove.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
const { Events, EmbedBuilder } = require('discord.js');
|
||||||
|
const { client } = require('../index.js');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: Events.GuildMemberRemove,
|
||||||
|
async execute(member) {
|
||||||
|
const removeMemberEmbed = new EmbedBuilder()
|
||||||
|
.setTitle(`${member.user.globalName} has left!`)
|
||||||
|
.setColor(process.env.EMBED_COLOR);
|
||||||
|
|
||||||
|
const channel = client.channels.cache.get(process.env.LOG_CHANNEL_ID);
|
||||||
|
channel.send({ embeds: [removeMemberEmbed] });
|
||||||
|
},
|
||||||
|
};
|
23
discordbot/events/interactionCreate.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
const { Events } = require('discord.js');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: Events.InteractionCreate,
|
||||||
|
async execute(interaction) {
|
||||||
|
if (interaction.isChatInputCommand() || interaction.isUserContextMenuCommand()) {
|
||||||
|
const command = interaction.client.commands.get(interaction.commandName);
|
||||||
|
|
||||||
|
if (!command) {
|
||||||
|
console.error(`No command matching ${interaction.commandName} was found.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await command.execute(interaction);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error executing ${interaction.commandName}`);
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
};
|
35
discordbot/events/messageCreate.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
const { Events } = require('discord.js');
|
||||||
|
const { Users } = require('../functions/models.js');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: Events.MessageCreate,
|
||||||
|
async execute(message) {
|
||||||
|
if (message.channelId === process.env.MINECRAFT_CHANNEL_ID && !message.author.bot) {
|
||||||
|
try {
|
||||||
|
const user = await Users.findOne({ where: { id: message.author.id } });
|
||||||
|
|
||||||
|
if (!user) return;
|
||||||
|
|
||||||
|
const team = await user.getTeam();
|
||||||
|
|
||||||
|
let tellraw;
|
||||||
|
|
||||||
|
if (!team) {
|
||||||
|
tellraw = `tellraw @a ["",{"text":"DC","color":"gray"},{"text":" ${user.rawUsername} > ${message.content}"}]`;
|
||||||
|
} else {
|
||||||
|
tellraw = `tellraw @a ["",{"text":"DC ","color":"dark_gray"},{"text":"[","color":"gray"},{"text":"${team.name}","color":"${team.color}"},{"text":"]","color":"gray"},{"text":" ${user.rawUsername} "},{"text":"> ${message.content}"}]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
await fetch(process.env.MINECRAFT_HOST + '/console', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'content-type': 'text/plain',
|
||||||
|
},
|
||||||
|
body: tellraw,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
17
discordbot/events/ready.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
const { Events } = require('discord.js');
|
||||||
|
const deployCommands = require('../functions/deployCommands');
|
||||||
|
const { Users, Team, Minecraft } = require('../functions/models');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: Events.ClientReady,
|
||||||
|
once: true,
|
||||||
|
execute(client) {
|
||||||
|
Users.sync();
|
||||||
|
Team.sync();
|
||||||
|
Minecraft.sync();
|
||||||
|
|
||||||
|
console.log(`Ready! Logged in as ${client.user.tag}`);
|
||||||
|
|
||||||
|
deployCommands();
|
||||||
|
},
|
||||||
|
};
|
45
discordbot/functions/deployCommands.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
const { REST, Routes } = require('discord.js');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const path = require('node:path');
|
||||||
|
|
||||||
|
module.exports = function deployCommands() {
|
||||||
|
const commands = [];
|
||||||
|
|
||||||
|
const commandsPath = path.join(__dirname, '../commands');
|
||||||
|
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
|
||||||
|
|
||||||
|
for (const file of commandFiles) {
|
||||||
|
const filePath = path.join(commandsPath, file);
|
||||||
|
const command = require(filePath);
|
||||||
|
if ('data' in command && 'execute' in command) {
|
||||||
|
commands.push(command.data.toJSON());
|
||||||
|
} else {
|
||||||
|
console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rest = new REST().setToken(process.env.DISCORD_TOKEN);
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
console.log(`Started refreshing ${commands.length} application (/) commands.`);
|
||||||
|
|
||||||
|
let data;
|
||||||
|
if (process.env.GUILD_ID === 'production') {
|
||||||
|
data = await rest.put(
|
||||||
|
Routes.applicationCommands(process.env.DISCORD_APPLICATION_ID),
|
||||||
|
{ body: commands },
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
data = await rest.put(
|
||||||
|
Routes.applicationGuildCommands(process.env.DISCORD_APPLICATION_ID, process.env.GUILD_ID),
|
||||||
|
{ body: commands },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Successfully reloaded ${data.length} application (/) commands.`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
};
|
9
discordbot/functions/embeds.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
const { EmbedBuilder } = require('discord.js');
|
||||||
|
|
||||||
|
const simpleEmbed = (content) => {
|
||||||
|
return new EmbedBuilder()
|
||||||
|
.setColor(process.env.EMBED_COLOR)
|
||||||
|
.setDescription(content);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { simpleEmbed };
|
73
discordbot/functions/models.js
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
const { PartialWebhookMixin } = require('discord.js');
|
||||||
|
const { sequelize } = require('../index');
|
||||||
|
const Sequelize = require('sequelize');
|
||||||
|
|
||||||
|
const Users = sequelize.define('users', {
|
||||||
|
id: {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
primaryKey: true,
|
||||||
|
unique: true,
|
||||||
|
},
|
||||||
|
useMinecraftUsername: {
|
||||||
|
type: Sequelize.BOOLEAN,
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
|
rawUsername: {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
},
|
||||||
|
moderator: {
|
||||||
|
type: Sequelize.BOOLEAN,
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
|
admin: {
|
||||||
|
type: Sequelize.BOOLEAN,
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const Team = sequelize.define('teams', {
|
||||||
|
name: {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
unique: true,
|
||||||
|
validate: {
|
||||||
|
len: [3, 16],
|
||||||
|
isAlphanumeric: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
validate: {
|
||||||
|
is: /^#?([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
textChannelId: {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
},
|
||||||
|
voiceChannelId: {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const Minecraft = sequelize.define('minecraft', {
|
||||||
|
uuid: {
|
||||||
|
type: Sequelize.UUID,
|
||||||
|
unique: true,
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
whitelisted: {
|
||||||
|
type: Sequelize.BOOLEAN,
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
type: Sequelize.STRING,
|
||||||
|
unique: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Minecraft.hasOne(Users);
|
||||||
|
Users.belongsTo(Minecraft);
|
||||||
|
|
||||||
|
Team.hasMany(Users, { as: 'members' });
|
||||||
|
Users.belongsTo(Team);
|
||||||
|
|
||||||
|
module.exports = { Users, Team, Minecraft };
|
29
discordbot/functions/utils.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
const applyUsername = async (user, member) => {
|
||||||
|
await user.reload();
|
||||||
|
|
||||||
|
let rawUsername = member.user.globalName;
|
||||||
|
|
||||||
|
if (user.useMinecraftUsername && user.minecraftUuid) {
|
||||||
|
const response = await fetch(`https://sessionserver.mojang.com/session/minecraft/profile/${user.minecraftUuid}`);
|
||||||
|
const minecraftProfile = await response.json();
|
||||||
|
|
||||||
|
rawUsername = minecraftProfile.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
user.rawUsername = rawUsername;
|
||||||
|
|
||||||
|
const username = await getUsername(user);
|
||||||
|
|
||||||
|
await user.save();
|
||||||
|
await member.setNickname(username.slice(0, 32));
|
||||||
|
|
||||||
|
return username;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUsername = async (user) => {
|
||||||
|
const team = await user.getTeam();
|
||||||
|
|
||||||
|
return team ? user.rawUsername + ' [' + team.name + ']' : user.rawUsername;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { applyUsername, getUsername };
|
@ -1,45 +1,29 @@
|
|||||||
const chalk = require('chalk');
|
|
||||||
const { Client, GatewayIntentBits, Collection } = require('discord.js');
|
const { Client, GatewayIntentBits, Collection } = require('discord.js');
|
||||||
const { Player } = require('discord-player');
|
const express = require('express');
|
||||||
|
const Sequelize = require('sequelize');
|
||||||
|
const dotenv = require('dotenv');
|
||||||
const fs = require('node:fs');
|
const fs = require('node:fs');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const dotenv = require('dotenv');
|
|
||||||
const express = require('express');
|
|
||||||
|
|
||||||
const createEmbed = require('./functions/createEmbed.js');
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
const log = {
|
|
||||||
Info: (message) => console.log(chalk.blue('INFO'), message),
|
|
||||||
Error: (message) => console.log(chalk.red('ERROR'), message),
|
|
||||||
Warn: (message) => console.log(chalk.hex('#FFA500'), message),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Register client and music events
|
// Configure database
|
||||||
const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent] });
|
const sequelize = new Sequelize('polarcraft', 'user', process.env.DATABSE_PASSWORD, {
|
||||||
client.player = new Player(client);
|
host: 'localhost',
|
||||||
|
dialect: 'sqlite',
|
||||||
require('./functions/player.js').registerEvents({ client, createEmbed });
|
logging: false,
|
||||||
|
// SQLite only
|
||||||
|
storage: 'database.sqlite',
|
||||||
// Express
|
|
||||||
const app = express();
|
|
||||||
app.use(express.json());
|
|
||||||
|
|
||||||
const minecraftRoute = require('./routes/Minecraft');
|
|
||||||
const userRoute = require('./routes/User');
|
|
||||||
const teamRoute = require('./routes/Team');
|
|
||||||
|
|
||||||
app.use('/minecraft', minecraftRoute);
|
|
||||||
app.use('/user', userRoute);
|
|
||||||
app.use('/team', teamRoute);
|
|
||||||
|
|
||||||
app.listen('4000', () => {
|
|
||||||
log.Info('Express app running');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
exports.sequelize = sequelize;
|
||||||
|
|
||||||
|
|
||||||
|
// Configure Discord
|
||||||
|
const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent] });
|
||||||
|
exports.client = client;
|
||||||
|
|
||||||
// Command handling
|
|
||||||
client.commands = new Collection();
|
client.commands = new Collection();
|
||||||
|
|
||||||
const commandsPath = path.join(__dirname, 'commands');
|
const commandsPath = path.join(__dirname, 'commands');
|
||||||
@ -52,12 +36,10 @@ for (const file of commandFiles) {
|
|||||||
if ('data' in command && 'execute' in command) {
|
if ('data' in command && 'execute' in command) {
|
||||||
client.commands.set(command.data.name, command);
|
client.commands.set(command.data.name, command);
|
||||||
} else {
|
} else {
|
||||||
log.Warn(`The command at ${filePath} is missing a required "data" or "execute" property.`);
|
console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Event handling
|
|
||||||
const eventsPath = path.join(__dirname, 'events');
|
const eventsPath = path.join(__dirname, 'events');
|
||||||
const eventFiles = fs.readdirSync(eventsPath).filter(file => file.endsWith('.js'));
|
const eventFiles = fs.readdirSync(eventsPath).filter(file => file.endsWith('.js'));
|
||||||
|
|
||||||
@ -65,12 +47,26 @@ for (const file of eventFiles) {
|
|||||||
const filePath = path.join(eventsPath, file);
|
const filePath = path.join(eventsPath, file);
|
||||||
const event = require(filePath);
|
const event = require(filePath);
|
||||||
if (event.once) {
|
if (event.once) {
|
||||||
client.once(event.name, (...args) => event.execute({ client, log, createEmbed }, ...args));
|
client.once(event.name, (...args) => event.execute(...args));
|
||||||
} else {
|
} else {
|
||||||
client.on(event.name, (...args) => event.execute({ client, log, createEmbed }, ...args));
|
client.on(event.name, (...args) => event.execute(...args));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Configure express
|
||||||
|
const app = express();
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
const verifyMinecraftRoute = require('./routes/verifyminecraft');
|
||||||
|
const messageRoute = require('./routes/message');
|
||||||
|
|
||||||
|
app.use('/verifyminecraft', verifyMinecraftRoute);
|
||||||
|
app.use('/message', messageRoute);
|
||||||
|
|
||||||
|
app.listen('3000', () => {
|
||||||
|
console.log('Express app is running');
|
||||||
|
});
|
||||||
|
|
||||||
client.login(process.env.DISCORD_TOKEN);
|
client.login(process.env.DISCORD_TOKEN);
|
||||||
|
|
||||||
module.exports.client = client;
|
|
5279
discordbot/package-lock.json
generated
@ -2,21 +2,20 @@
|
|||||||
"name": "discordbot",
|
"name": "discordbot",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.ts",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tsx watch src/index.ts",
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
"start": "node dist/index.js",
|
|
||||||
"build": "tsup src/index.ts --minify"
|
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "Xeovalyte",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"discord.js": "^14.11.0",
|
"discord.js": "^14.12.1",
|
||||||
"dotenv": "^16.3.1"
|
"dotenv": "^16.3.1",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"sequelize": "^6.32.1",
|
||||||
|
"sqlite3": "^5.1.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"tsup": "^7.1.0",
|
"eslint": "^8.46.0"
|
||||||
"tsx": "^3.12.7",
|
|
||||||
"typescript": "^5.1.3"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
56
discordbot/routes/message.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const { WebhookClient, EmbedBuilder } = require('discord.js');
|
||||||
|
const { getUsername } = require('../functions/utils.js');
|
||||||
|
const { Users } = require('../functions/models.js');
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
const webhookClient = new WebhookClient({ url: process.env.MINECRAFT_WEBHOOK_URL });
|
||||||
|
|
||||||
|
router.post('/player', async (req, res) => {
|
||||||
|
const { content, uuid } = req.body;
|
||||||
|
|
||||||
|
if (!uuid || !content) return res.status(400).send({ errorMessage: 'uuid and content are required' });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const user = await Users.findOne({ where: { minecraftUUID: uuid } });
|
||||||
|
|
||||||
|
const username = await getUsername(user);
|
||||||
|
|
||||||
|
webhookClient.send({
|
||||||
|
content,
|
||||||
|
username,
|
||||||
|
avatarURL: 'https://api.mineatar.io/face/' + uuid + '?scale=8',
|
||||||
|
});
|
||||||
|
|
||||||
|
res.send({ status: 'ok' });
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
|
||||||
|
res.status(500).send({ errorMessage: 'Error while sending player message' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/game', async (req, res) => {
|
||||||
|
const { content, uuid } = req.body;
|
||||||
|
|
||||||
|
if (!uuid || !content) return res.status(400).send({ errorMessage: 'uuid and content are required' });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const messageEmbed = new EmbedBuilder()
|
||||||
|
.setColor(process.env.EMBED_COLOR)
|
||||||
|
.setAuthor({ name: content, iconURL: 'https://api.mineatar.io/face/' + uuid + '?scale=8' });
|
||||||
|
|
||||||
|
webhookClient.send({
|
||||||
|
embeds: [messageEmbed],
|
||||||
|
username: 'Server',
|
||||||
|
});
|
||||||
|
|
||||||
|
res.send({ status: 'ok' });
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
|
||||||
|
res.status(500).send({ errorMessage: 'Error while sending player message' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
47
discordbot/routes/verifyminecraft.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const { Minecraft, Users } = require('../functions/models.js');
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.post('/', async (req, res) => {
|
||||||
|
const { uuid } = req.body;
|
||||||
|
|
||||||
|
if (!uuid) return res.status(400).send({ errorMessage: 'UUID is required' });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const user = await Users.findOne({ where: { minecraftUUID: uuid } });
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
const minecraftCol = await Minecraft.findOrCreate({
|
||||||
|
where: { uuid },
|
||||||
|
defaults: {
|
||||||
|
code: generateCode().toString(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.send({ code: minecraftCol[0].code, whitelisted: minecraftCol[0].whitelisted });
|
||||||
|
}
|
||||||
|
|
||||||
|
const team = await user.getTeam();
|
||||||
|
|
||||||
|
let username;
|
||||||
|
if (!team) {
|
||||||
|
username = user.rawUsername;
|
||||||
|
} else {
|
||||||
|
username = '<gray>[</gray>' + `<color:${team.color}>${team.name}</color>` + '<gray>] </gray>' + user.rawUsername;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
res.send({ whitelisted: true, username, rawUsername: user.rawUsername });
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
|
||||||
|
res.status(500).send({ errorMessage: 'Error while verifing minecraft uuid' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const generateCode = () => {
|
||||||
|
return Math.floor(100000 + Math.random() * 900000);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = router;
|
@ -1,10 +0,0 @@
|
|||||||
import { CommandInteraction, SlashCommandBuilder, Client } from "discord.js";
|
|
||||||
|
|
||||||
export const data = new SlashCommandBuilder()
|
|
||||||
.setName("ping")
|
|
||||||
.setDescription("Replies with Pong!");
|
|
||||||
|
|
||||||
export async function execute({ interaction, client, createEmbed }: { interaction: CommandInteraction, client: Client, createEmbed: Function }) {
|
|
||||||
const reply = await interaction.reply({ embeds: [createEmbed(`Websocket heartbeat: **${client.ws.ping}ms**\n Roundtrip latency: **Pinging...**`)], fetchReply: true, ephemeral: true });
|
|
||||||
interaction.editReply({ embeds: [createEmbed(`Websocket heartbeat: **${client.ws.ping}ms**\n Roundtrip latency: **${reply.createdTimestamp - interaction.createdTimestamp}ms**`)] });
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
import { ColorResolvable } from "discord.js";
|
|
||||||
import dotenv from "dotenv";
|
|
||||||
|
|
||||||
dotenv.config();
|
|
||||||
|
|
||||||
const { DISCORD_TOKEN, DISCORD_CLIENT_ID, DISCORD_GUILD_ID } = process.env;
|
|
||||||
|
|
||||||
if (!DISCORD_TOKEN || !DISCORD_CLIENT_ID) {
|
|
||||||
throw new Error("Missing environment variables");
|
|
||||||
}
|
|
||||||
|
|
||||||
const EMBED_COLOR: ColorResolvable = '#0080ff'
|
|
||||||
|
|
||||||
export const config = {
|
|
||||||
DISCORD_TOKEN,
|
|
||||||
DISCORD_CLIENT_ID,
|
|
||||||
DISCORD_GUILD_ID,
|
|
||||||
EMBED_COLOR
|
|
||||||
};
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
|||||||
import { Events, Client, BaseInteraction } from 'discord.js'
|
|
||||||
|
|
||||||
export const name = Events.InteractionCreate
|
|
||||||
|
|
||||||
export const execute = async ({ client, createEmbed }: { client: Client, createEmbed: Function }, interaction: BaseInteraction) => {
|
|
||||||
if (interaction.isChatInputCommand()) {
|
|
||||||
const command = interaction.client.commands.get(interaction.commandName);
|
|
||||||
|
|
||||||
if (!command) {
|
|
||||||
return console.error(`No command matching ${interaction.commandName} was found`);
|
|
||||||
} else {
|
|
||||||
console.info(`${interaction.user.username} executed command ${interaction.commandName}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await command.execute({ interaction, client, createEmbed });
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
|
|
||||||
await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
import { Events, Client } from 'discord.js'
|
|
||||||
import registerCommands from '../registerCommands'
|
|
||||||
|
|
||||||
export const name = Events.ClientReady
|
|
||||||
|
|
||||||
export const once = true
|
|
||||||
|
|
||||||
export const execute = async ({ client }: { client: Client }) => {
|
|
||||||
console.log(`Ready! Logged in as ${client.user.tag}`)
|
|
||||||
|
|
||||||
registerCommands()
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
import { Client, GatewayIntentBits, Collection, EmbedBuilder } from 'discord.js'
|
|
||||||
import fs from 'node:fs'
|
|
||||||
import path from 'node:path'
|
|
||||||
import { config } from './config'
|
|
||||||
|
|
||||||
const client = new Client({
|
|
||||||
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent]
|
|
||||||
})
|
|
||||||
|
|
||||||
const createEmbed = (description: string) => {
|
|
||||||
return new EmbedBuilder()
|
|
||||||
.setColor(config.EMBED_COLOR)
|
|
||||||
.setDescription(description);
|
|
||||||
};
|
|
||||||
|
|
||||||
client.commands = new Collection()
|
|
||||||
|
|
||||||
const commandsPath = path.join(__dirname, 'commands');
|
|
||||||
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.ts'));
|
|
||||||
|
|
||||||
for (const file of commandFiles) {
|
|
||||||
const filePath = path.join(commandsPath, file);
|
|
||||||
const command = require(filePath);
|
|
||||||
|
|
||||||
if ('data' in command && 'execute' in command) {
|
|
||||||
client.commands.set(command.data.name, command);
|
|
||||||
} else {
|
|
||||||
console.error(`The command at ${filePath} is missing a required "data" or "execute" property.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const eventsPath = path.join(__dirname, 'events');
|
|
||||||
const eventFiles = fs.readdirSync(eventsPath).filter(file => file.endsWith('.ts'));
|
|
||||||
|
|
||||||
for (const file of eventFiles) {
|
|
||||||
const filePath = path.join(eventsPath, file);
|
|
||||||
const event = require(filePath);
|
|
||||||
if (event.once) {
|
|
||||||
client.once(event.name, (...args) => event.execute({ client, createEmbed }, ...args));
|
|
||||||
} else {
|
|
||||||
client.on(event.name, (...args) => event.execute({ client, createEmbed }, ...args));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.login(config.DISCORD_TOKEN);
|
|
@ -1,54 +0,0 @@
|
|||||||
import fs from 'node:fs'
|
|
||||||
import path from 'node:path'
|
|
||||||
import { REST, Routes } from 'discord.js'
|
|
||||||
import { config } from './config'
|
|
||||||
|
|
||||||
type Command = {
|
|
||||||
data: any
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async () => {
|
|
||||||
const commands = []
|
|
||||||
|
|
||||||
const commandsPath = path.join(__dirname, 'commands')
|
|
||||||
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('ts'))
|
|
||||||
|
|
||||||
for (const file of commandFiles) {
|
|
||||||
const filePath = path.join(commandsPath, file)
|
|
||||||
const command = await import(filePath) as Command
|
|
||||||
|
|
||||||
if ('data' in command && 'execute' in command) {
|
|
||||||
commands.push(command.data.toJSON())
|
|
||||||
} else {
|
|
||||||
console.error(`The command at ${filePath} is missing a required "data" or "execute" property`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const rest = new REST().setToken(config.DISCORD_TOKEN)
|
|
||||||
|
|
||||||
try {
|
|
||||||
console.log(`Started refreshing ${commands.length} application (/) commands.`);
|
|
||||||
|
|
||||||
// The put method is used to fully refresh all commands in the guild with the current set
|
|
||||||
if (config.DISCORD_GUILD_ID) {
|
|
||||||
const data: any = await rest.put(
|
|
||||||
Routes.applicationGuildCommands(config.DISCORD_CLIENT_ID, config.DISCORD_GUILD_ID),
|
|
||||||
{ body: commands },
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(`Successfully reloaded ${data.length} guild (/) commands.`);
|
|
||||||
} else {
|
|
||||||
|
|
||||||
const data: any = await rest.put(
|
|
||||||
Routes.applicationCommands(config.DISCORD_CLIENT_ID),
|
|
||||||
{ body: commands },
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(`Successfully reloaded ${data.length} global (/) commands.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
// And of course, make sure you catch and log any errors!
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ES2020",
|
|
||||||
"module": "commonjs",
|
|
||||||
"rootDir": "./src",
|
|
||||||
"outDir": "./dist",
|
|
||||||
"removeComments": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"strict": true,
|
|
||||||
"strictNullChecks": true,
|
|
||||||
"skipLibCheck": true
|
|
||||||
}
|
|
||||||
}
|
|
7
discordbot/types.d.ts
vendored
@ -1,7 +0,0 @@
|
|||||||
import { Collection } from 'discord.js'
|
|
||||||
|
|
||||||
declare module 'discord.js' {
|
|
||||||
export interface Client {
|
|
||||||
commands: Collection<unknown, any>
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"root": true,
|
|
||||||
"extends": ["@nuxt/eslint-config", "plugin:tailwindcss/recommended"],
|
|
||||||
"rules": {
|
|
||||||
"vue/max-attributes-per-line": ["error", {
|
|
||||||
"singleline": {
|
|
||||||
"max": 4
|
|
||||||
},
|
|
||||||
"multiline": {
|
|
||||||
"max": 2
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
"vue/multi-word-component-names": "off",
|
|
||||||
"vue/singleline-html-element-content-newline": "off",
|
|
||||||
"no-undef": "off"
|
|
||||||
}
|
|
||||||
}
|
|
9
web/.gitignore
vendored
@ -1,9 +0,0 @@
|
|||||||
node_modules
|
|
||||||
*.log*
|
|
||||||
.nuxt
|
|
||||||
.nitro
|
|
||||||
.cache
|
|
||||||
.output
|
|
||||||
.env
|
|
||||||
dist
|
|
||||||
.DS_Store
|
|
@ -1,2 +0,0 @@
|
|||||||
shamefully-hoist=true
|
|
||||||
strict-peer-dependencies=false
|
|
@ -1,9 +0,0 @@
|
|||||||
FROM node:18-alpine
|
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
|
||||||
|
|
||||||
COPY .output .
|
|
||||||
|
|
||||||
EXPOSE 3000
|
|
||||||
|
|
||||||
CMD [ "node", "server/index.mjs" ]
|
|
@ -1,42 +0,0 @@
|
|||||||
# Nuxt 3 Minimal Starter
|
|
||||||
|
|
||||||
Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
Make sure to install the dependencies:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# yarn
|
|
||||||
yarn install
|
|
||||||
|
|
||||||
# npm
|
|
||||||
npm install
|
|
||||||
|
|
||||||
# pnpm
|
|
||||||
pnpm install
|
|
||||||
```
|
|
||||||
|
|
||||||
## Development Server
|
|
||||||
|
|
||||||
Start the development server on `http://localhost:3000`
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## Production
|
|
||||||
|
|
||||||
Build the application for production:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
Locally preview production build:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run preview
|
|
||||||
```
|
|
||||||
|
|
||||||
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
|
@ -1,5 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="h-screen w-full overflow-y-auto">
|
|
||||||
<NuxtLayout />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -1,17 +0,0 @@
|
|||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
@layer components {
|
|
||||||
.sidebar-item {
|
|
||||||
@apply font-medium text-lg w-full px-4 py-2 rounded hover:bg-white hover:bg-opacity-5 hover:cursor-pointer flex items-center
|
|
||||||
}
|
|
||||||
|
|
||||||
.highlight {
|
|
||||||
@apply bg-primary bg-opacity-20 px-2 rounded
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.router-link-active {
|
|
||||||
background-color: rgba(0, 0, 0, 20%)
|
|
||||||
}
|
|
Before Width: | Height: | Size: 11 MiB |
@ -1,43 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex w-full flex-col items-center text-primary">
|
|
||||||
<div v-if="!user.minecraft.uuid" class="flex flex-col items-center">
|
|
||||||
<p class="mb-10 max-w-xl text-sm sm:text-base">
|
|
||||||
Je bent momenteel niet gewhitelist. Om toegang te krijgen tot de Minecraft server moet je in Minecraft naar de
|
|
||||||
server met het ip <span class="highlight">play.polarcraft.xeovalyte.com</span> gaan. Vervolgens krijg je een code
|
|
||||||
te zien, vul deze code hieronder in.
|
|
||||||
</p>
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<Input v-model="code">Code</Input>
|
|
||||||
<Button @click="submitCode">Submit</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
definePageMeta({
|
|
||||||
middleware: ["auth"]
|
|
||||||
})
|
|
||||||
|
|
||||||
const user = useState('user')
|
|
||||||
const code = ref('')
|
|
||||||
|
|
||||||
const submitCode = async () => {
|
|
||||||
try {
|
|
||||||
const response = await $fetch('/api/minecraft/whitelist', {
|
|
||||||
method: 'POST',
|
|
||||||
body: {
|
|
||||||
code: code.value
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
user.value.minecraft.uuid = response.uuid
|
|
||||||
user.value.minecraft.username = response.username
|
|
||||||
|
|
||||||
useToast().success('Succesvol gewhitelist')
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
useToast().error(e.statusMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,19 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex items-center border-b border-b-gray-700 bg-neutral-800 px-5">
|
|
||||||
<h1 class="text-2xl font-bold text-primary">Polarcraft</h1>
|
|
||||||
<Button outline class="ml-auto" @click="logout">Logout</Button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
const logout = async () => {
|
|
||||||
try {
|
|
||||||
await useFetch('/api/auth/logout')
|
|
||||||
|
|
||||||
navigateTo('/login')
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err)
|
|
||||||
alert(err.statusMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,35 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex flex-col gap-1 bg-neutral-800 px-2 pb-14 pt-5 text-gray-300">
|
|
||||||
<NuxtLink to="/" class="sidebar-item">
|
|
||||||
<Icon size="1.5em" name="ph:house" class="mr-3" />
|
|
||||||
Home
|
|
||||||
</NuxtLink>
|
|
||||||
<NuxtLink to="/team" class="sidebar-item">
|
|
||||||
<Icon size="1.5em" name="ph:users-three" class="mr-3" />
|
|
||||||
Team
|
|
||||||
</NuxtLink>
|
|
||||||
<NuxtLink to="/player-store" class="sidebar-item">
|
|
||||||
<Icon size="1.5em" name="ph:storefront" class="mr-3" />
|
|
||||||
Player stores
|
|
||||||
</NuxtLink>
|
|
||||||
<NuxtLink to="/map" class="sidebar-item">
|
|
||||||
<Icon size="1.5em" name="ph:map-trifold" class="mr-3" />
|
|
||||||
Map
|
|
||||||
</NuxtLink>
|
|
||||||
<div v-if="user && user.role.admin" class="mt-auto space-y-1">
|
|
||||||
<h2 class="ml-2 text-gray-400">Moderation</h2>
|
|
||||||
<NuxtLink to="/mod/users" class="sidebar-item">
|
|
||||||
<Icon size="1.5em" name="ph:users-three" class="mr-3" />
|
|
||||||
Users
|
|
||||||
</NuxtLink>
|
|
||||||
<NuxtLink to="/mod/server" class="sidebar-item">
|
|
||||||
<Icon size="1.5em" name="ph:cpu" class="mr-3" />
|
|
||||||
Server Control
|
|
||||||
</NuxtLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
const user = useState('user')
|
|
||||||
</script>
|
|
@ -1,180 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Modal v-if="modalOpen" title="Invite team member" @close="modalOpen = false" @submit="modalOpen = false">
|
|
||||||
<div>
|
|
||||||
<h2 class="text-lg font-bold text-primary">Users</h2>
|
|
||||||
<div class="h-48 space-y-3 overflow-y-auto">
|
|
||||||
<div v-for="unaffiliatedUser in unaffilatedUsers" :key="unaffiliatedUser._id.toString()" class="flex items-center rounded bg-neutral-700 px-3 py-1 text-gray-200">
|
|
||||||
{{ unaffiliatedUser.username }}
|
|
||||||
<Button v-if="!unaffiliatedUser.teamInvites.includes(user.team.id)" class="ml-auto" @click="inviteUser(unaffiliatedUser)">Invite</Button>
|
|
||||||
<Button v-else class="ml-auto" type="danger" @click="cancelInvite(unaffiliatedUser)">Cancel Invite</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
<Modal v-if="editTeamModal.open" title="Edit team" @close="editTeamModal.open = false" @submit="editTeam">
|
|
||||||
<Input v-model="editTeamModal.name" background-class="bg-neutral-800" class="w-full max-w-sm">Naam / Prefix</Input>
|
|
||||||
<Colorpicker v-model="editTeamModal.color" input-background-class="bg-neutral-800" class="w-full max-w-sm" />
|
|
||||||
</Modal>
|
|
||||||
<div class="mx-auto my-10 max-w-2xl">
|
|
||||||
<h2 class="mb-2 text-xl font-bold text-primary">Team Information</h2>
|
|
||||||
<div class="rounded border-[1px] border-primary px-5 py-2 text-primary">
|
|
||||||
<table class="w-full table-auto">
|
|
||||||
<tbody class="divide-y divide-gray-700">
|
|
||||||
<tr>
|
|
||||||
<td class="py-3">Name</td>
|
|
||||||
<td class="font-bold">{{ team.name }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="py-3">Color</td>
|
|
||||||
<td class="font-bold">{{ team.color }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td class="py-3">ID</td>
|
|
||||||
<td class="font-bold">{{ team._id }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class="mb-10 mt-5 flex justify-center gap-x-3">
|
|
||||||
<Button type="danger" @click="leaveTeam">Leave Team</Button>
|
|
||||||
<Button v-if="user.team.admin" @click="openTeamModal">Edit Team</Button>
|
|
||||||
</div>
|
|
||||||
<h2 class="mb-2 text-xl font-bold text-primary">Team Members</h2>
|
|
||||||
<div class="space-y-5 rounded border-[1px] border-primary p-5 text-primary">
|
|
||||||
<div v-for="teamMember in teamMembers" :key="teamMember._id" class="flex h-12 items-center rounded bg-neutral-800 px-5 font-bold text-gray-200">
|
|
||||||
{{ teamMember.username }}
|
|
||||||
<span v-if="teamMember.team.admin" class="ml-3 text-sm text-gray-400">Admin</span>
|
|
||||||
<div v-if="user.team.admin" class="ml-auto">
|
|
||||||
<Button v-if="!teamMember.team.admin && teamMember._id !== user._id" @click="promoteUser(teamMember)">Promote</Button>
|
|
||||||
<Button v-if="teamMember.team.admin && teamMember._id !== user._id" @click="demoteUser(teamMember)">Demote</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="user.team.admin" class="rounded border-2 border-dashed border-neutral-500 bg-neutral-800 p-3 text-center font-bold text-gray-200 hover:cursor-pointer hover:bg-neutral-700" @click="modalOpen = true">
|
|
||||||
Invite new member
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
const user = useState('user');
|
|
||||||
const { data: team } = await useFetch('/api/team');
|
|
||||||
const { data: teamMembers } = await useFetch('/api/team/members');
|
|
||||||
const { data: unaffilatedUsers } = await useFetch('/api/team/unaffiliated')
|
|
||||||
|
|
||||||
const modalOpen = ref(false)
|
|
||||||
|
|
||||||
const editTeamModal = ref({
|
|
||||||
open: false,
|
|
||||||
name: '',
|
|
||||||
color: '',
|
|
||||||
})
|
|
||||||
|
|
||||||
const openTeamModal = () => {
|
|
||||||
editTeamModal.value.name = team.value.name
|
|
||||||
editTeamModal.value.color = team.value.color
|
|
||||||
editTeamModal.value.open = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const leaveTeam = async () => {
|
|
||||||
try {
|
|
||||||
await $fetch('/api/team/leave');
|
|
||||||
|
|
||||||
user.value.team = null;
|
|
||||||
|
|
||||||
useToast().success('Succesvol team verlaten')
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
useToast().error(e.statusMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const editTeam = async () => {
|
|
||||||
try {
|
|
||||||
await $fetch('/api/team/edit', {
|
|
||||||
method: 'POST',
|
|
||||||
body: {
|
|
||||||
name: editTeamModal.value.name,
|
|
||||||
color: editTeamModal.value.color,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
team.value.name = editTeamModal.value.name
|
|
||||||
team.value.color = editTeamModal.value.color
|
|
||||||
|
|
||||||
useToast().success('Team is succesvol bewerkt')
|
|
||||||
|
|
||||||
editTeamModal.value.open = false
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
useToast().error(e.statusMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const inviteUser = async (usr) => {
|
|
||||||
try {
|
|
||||||
await $fetch('/api/team/invite', {
|
|
||||||
method: 'POST',
|
|
||||||
body: { id: usr._id }
|
|
||||||
});
|
|
||||||
|
|
||||||
usr.teamInvites.push(user.value.team.id);
|
|
||||||
|
|
||||||
useToast().success(`Invited ${usr.username} succesvol`)
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
useToast().error(e.statusMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const cancelInvite = async (usr) => {
|
|
||||||
try {
|
|
||||||
await $fetch('/api/team/cancelinvite', {
|
|
||||||
method: 'POST',
|
|
||||||
body: { id: usr._id }
|
|
||||||
});
|
|
||||||
|
|
||||||
usr.teamInvites = usr.teamInvites.filter(a => a !== user.value.team.id);
|
|
||||||
|
|
||||||
useToast().success('Succesvol invite geannuleerd')
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
useToast().error(e.statusMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const promoteUser = async (usr) => {
|
|
||||||
try {
|
|
||||||
await $fetch('/api/team/promote', {
|
|
||||||
method: 'POST',
|
|
||||||
body: { userId: usr._id }
|
|
||||||
});
|
|
||||||
|
|
||||||
usr.team.admin = true
|
|
||||||
|
|
||||||
console.log(usr)
|
|
||||||
console.log(teamMembers)
|
|
||||||
|
|
||||||
useToast().success('Succesvol gebruiker gepromoveerd')
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
useToast().error(e.statusMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const demoteUser = async (usr) => {
|
|
||||||
try {
|
|
||||||
await $fetch('/api/team/demote', {
|
|
||||||
method: 'POST',
|
|
||||||
body: { userId: usr._id }
|
|
||||||
});
|
|
||||||
|
|
||||||
usr.team.admin = false
|
|
||||||
|
|
||||||
useToast().success('Succesvol gebruiker gedegradeerd')
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
useToast().error(e.statusMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,84 +0,0 @@
|
|||||||
<template>
|
|
||||||
<p class="my-10 text-center text-gray-300">
|
|
||||||
Je zit momenteel niet in een team. Maak een team aan of wacht tot dat je geinvite wordt
|
|
||||||
</p>
|
|
||||||
<div class="mb-5 flex w-full justify-center gap-10">
|
|
||||||
<span
|
|
||||||
class="font-bold text-primary hover:cursor-pointer" :class="{ 'underline underline-offset-4': !createTeam.show }"
|
|
||||||
@click="createTeam.show = false"
|
|
||||||
>
|
|
||||||
Team Invites
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="font-bold text-primary hover:cursor-pointer" :class="{ 'underline underline-offset-4': createTeam.show }"
|
|
||||||
@click="createTeam.show = true"
|
|
||||||
>
|
|
||||||
Create Team
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div v-if="!createTeam.show" class="text-center text-gray-300">
|
|
||||||
<h2 v-if="!user.teamInvites.length">You don't have any team invites</h2>
|
|
||||||
<div v-else class="mx-auto flex max-w-lg flex-col">
|
|
||||||
<div v-for="team in filteredTeams" :key="team._id" class="flex items-center rounded bg-neutral-800 px-3 py-1 text-left" @click="acceptInvite(team)">
|
|
||||||
<span>
|
|
||||||
{{ team.name }}
|
|
||||||
</span>
|
|
||||||
<Button class="ml-auto">Accept</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="flex w-full flex-col items-center gap-5">
|
|
||||||
<Input v-model="createTeam.name" class="w-full max-w-sm">Naam</Input>
|
|
||||||
<Colorpicker v-model="createTeam.color" class="w-full max-w-sm" />
|
|
||||||
<Button @click="handleCreateTeam">Create Team</Button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
const user = useState('user')
|
|
||||||
|
|
||||||
const teams = await $fetch('/api/team/all')
|
|
||||||
|
|
||||||
const filteredTeams = computed(() => {
|
|
||||||
return teams.filter(a => user.value.teamInvites.includes(a._id));
|
|
||||||
})
|
|
||||||
|
|
||||||
const createTeam = ref({
|
|
||||||
show: false,
|
|
||||||
name: '',
|
|
||||||
color: '',
|
|
||||||
})
|
|
||||||
|
|
||||||
const acceptInvite = async (team) => {
|
|
||||||
try {
|
|
||||||
const response = await $fetch('/api/team/acceptinvite', {
|
|
||||||
method: 'POST',
|
|
||||||
body: { teamId: team._id }
|
|
||||||
})
|
|
||||||
|
|
||||||
user.value.team = { id: response._id, admin: false }
|
|
||||||
|
|
||||||
useToast().success('Succesvol lid geworden van het team')
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
useToast().error(e.statusMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleCreateTeam = async () => {
|
|
||||||
try {
|
|
||||||
const response = await $fetch('/api/team/create', {
|
|
||||||
method: 'POST',
|
|
||||||
body: { teamName: createTeam.value.name, teamColor: createTeam.value.color }
|
|
||||||
})
|
|
||||||
|
|
||||||
user.value.team = { id: response.insertedId.toString(), admin: true }
|
|
||||||
|
|
||||||
useToast().success('Succesvol team gemaakt')
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
useToast().error(e.statusMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,3 +0,0 @@
|
|||||||
<template>
|
|
||||||
<NuxtPage />
|
|
||||||
</template>
|
|
@ -1,20 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="h-full bg-neutral-900">
|
|
||||||
<div v-if="user" class="hidden h-full grid-cols-desktoplayout grid-rows-desktoplayout sm:grid">
|
|
||||||
<LayoutNavbar class="col-span-2" />
|
|
||||||
<LayoutSidebar v-if="user.minecraft.uuid" class="" />
|
|
||||||
<div class="overflow-y-auto px-10" :class="{ 'col-span-2': !user.minecraft.uuid }">
|
|
||||||
<NuxtPage />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="user" class="h-full sm:hidden">
|
|
||||||
<div class="overflow-y-auto p-2">
|
|
||||||
<NuxtPage />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
const user = useState('user')
|
|
||||||
</script>
|
|
@ -1,17 +0,0 @@
|
|||||||
export default defineNuxtRouteMiddleware(async (to) => {
|
|
||||||
if (process.server) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const user = await $fetch('/api/auth/user')
|
|
||||||
useState('user', () => user)
|
|
||||||
|
|
||||||
if (to.meta.moderator && !user.role.moderator) return navigateTo('/')
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err)
|
|
||||||
|
|
||||||
useState('user', () => null)
|
|
||||||
|
|
||||||
return navigateTo('/login')
|
|
||||||
}
|
|
||||||
})
|
|
@ -1,40 +0,0 @@
|
|||||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
|
||||||
export default defineNuxtConfig({
|
|
||||||
extends: 'node_modules/@xeovalyte/nuxt-xvui',
|
|
||||||
devtools: true,
|
|
||||||
ssr: false,
|
|
||||||
modules: [
|
|
||||||
'@nuxtjs/tailwindcss',
|
|
||||||
'@xeovalyte/nuxt-xvtoast',
|
|
||||||
'nuxt-icon',
|
|
||||||
'@nuxtjs/tailwindcss',
|
|
||||||
'@vueuse/nuxt',
|
|
||||||
'@nuxt/image-edge'
|
|
||||||
],
|
|
||||||
app: {
|
|
||||||
head: {
|
|
||||||
link: [
|
|
||||||
{ rel: "apple-touch-icon", sizes: "180x180", href: "/apple-touch-icon.png" },
|
|
||||||
{ rel: "icon", sizes: "32x32", type: "image/png", href: "/favicon-32x32.png" },
|
|
||||||
{ rel: "icon", sizes: "16x16", type: "image/png", href: "/favicon-16x16.png" },
|
|
||||||
{ rel: "manifest", href: "/site.webmanifest" },
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
runtimeConfig: {
|
|
||||||
discordId: '',
|
|
||||||
discordSecret: '',
|
|
||||||
discordHost: '',
|
|
||||||
jwtSecret: '',
|
|
||||||
dbUrl: '',
|
|
||||||
mineckerHost: '',
|
|
||||||
mineckerApiKey: '',
|
|
||||||
rconPassword: '',
|
|
||||||
rconPort: '25575',
|
|
||||||
rconHost: 'localhost',
|
|
||||||
redirectURI: 'http://localhost:3000/api/auth',
|
|
||||||
public: {
|
|
||||||
redirectUrl: 'https://discord.com/api/oauth2/authorize?client_id=1052974736432443432&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth&response_type=code&scope=identify',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
25302
web/package-lock.json
generated
@ -1,36 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "nuxt-app",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"build": "nuxt build",
|
|
||||||
"dev": "nuxt dev",
|
|
||||||
"generate": "nuxt generate",
|
|
||||||
"preview": "nuxt preview",
|
|
||||||
"postinstall": "nuxt prepare",
|
|
||||||
"lint": "eslint .",
|
|
||||||
"lint-fix": "eslint . --fix"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@nuxt/devtools": "^0.5.0",
|
|
||||||
"@nuxt/image-edge": "^1.0.0-28059208.2abef1b",
|
|
||||||
"@nuxtjs/eslint-module": "^4.1.0",
|
|
||||||
"@nuxtjs/tailwindcss": "^6.7.0",
|
|
||||||
"@types/node": "^20",
|
|
||||||
"@vueuse/components": "^10.1.2",
|
|
||||||
"@vueuse/core": "^10.1.2",
|
|
||||||
"@vueuse/nuxt": "^10.1.2",
|
|
||||||
"eslint": "^8.41.0",
|
|
||||||
"eslint-plugin-tailwindcss": "^3.12.0",
|
|
||||||
"nuxt": "^3.5.0",
|
|
||||||
"nuxt-icon": "^0.4.1"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@nuxt/eslint-config": "^0.1.1",
|
|
||||||
"@xeovalyte/nuxt-xvtoast": "^1.1.3",
|
|
||||||
"@xeovalyte/nuxt-xvui": "git+https://gitea.xeovalyte.dev/xeovalyte/nuxt-xvui.git",
|
|
||||||
"jsonwebtoken": "^9.0.0",
|
|
||||||
"minecraft-server-util": "^5.4.2",
|
|
||||||
"mongodb": "^5.5.0",
|
|
||||||
"socket.io-client": "^4.6.1"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,120 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex h-full w-full flex-col text-primary">
|
|
||||||
<h1 class="mb-10 mt-20 text-center text-5xl font-bold">Welkom, {{ user.username }}</h1>
|
|
||||||
<Whitelist v-if="!user.minecraft.uuid" />
|
|
||||||
<div v-else class="flex w-full flex-wrap justify-evenly gap-10">
|
|
||||||
<div class="flex w-full max-w-xl flex-col gap-3">
|
|
||||||
<h2 class="text-xl font-bold text-primary">Discord Information <span v-if="!user.useMinecraftUsername" class="font-normal">(Default)</span></h2>
|
|
||||||
<div class="flex gap-3">
|
|
||||||
<img :src="'https://cdn.discordapp.com/avatars/' + user.discord.id + '/' + user.discord.avatarHash + '.png'" class="aspect-square w-24 rounded shadow">
|
|
||||||
<div class="flex w-full rounded border-2 border-primary p-4">
|
|
||||||
<ul class="my-auto">
|
|
||||||
<li>Username: <b>{{ user.discord.username }}</b></li>
|
|
||||||
<li>ID: <b>{{ user.discord.id }}</b></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mt-2 flex w-full justify-center gap-4">
|
|
||||||
<Button @click="refreshDiscordUsername">
|
|
||||||
Refresh Username
|
|
||||||
</Button>
|
|
||||||
<Button v-if="user.useMinecraftUsername" @click="setDefaultUsername('discord')">
|
|
||||||
Set Default
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex w-full max-w-xl flex-col gap-3">
|
|
||||||
<h2 class="text-xl font-bold text-primary">Minecraft Information <span v-if="user.useMinecraftUsername" class="font-normal">(Default)</span></h2>
|
|
||||||
<div class="flex gap-3">
|
|
||||||
<img :src="'https://api.mineatar.io/face/' + user.minecraft.uuid + '?scale=16'" class="aspect-square w-24 rounded shadow">
|
|
||||||
<div class="flex w-full rounded border-2 border-primary p-4">
|
|
||||||
<ul class="my-auto">
|
|
||||||
<li>Username: <b>{{ user.minecraft.username }}</b></li>
|
|
||||||
<li>UUID: <b>{{ user.minecraft.uuid }}</b></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mt-2 flex w-full justify-center gap-4">
|
|
||||||
<Button type="danger" @click="removeWhitelist">
|
|
||||||
Remove from whitelist
|
|
||||||
</Button>
|
|
||||||
<Button @click="refreshMinecraftUsername">
|
|
||||||
Refresh Username
|
|
||||||
</Button>
|
|
||||||
<Button v-if="!user.useMinecraftUsername" @click="setDefaultUsername('minecraft')">
|
|
||||||
Set Default
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
definePageMeta({
|
|
||||||
middleware: ["auth"],
|
|
||||||
})
|
|
||||||
|
|
||||||
useHead({ title: 'Polarcraft' })
|
|
||||||
|
|
||||||
const user = useState('user')
|
|
||||||
|
|
||||||
const refreshMinecraftUsername = async () => {
|
|
||||||
try {
|
|
||||||
const response = await $fetch('/api/minecraft/refreshusername')
|
|
||||||
|
|
||||||
user.value.minecraft.username = response.username
|
|
||||||
|
|
||||||
useToast().success('Gebruikersnaam is ververst')
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
|
|
||||||
useToast().error(e.statusMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const refreshDiscordUsername = async () => {
|
|
||||||
try {
|
|
||||||
const response = await $fetch('/api/discord/refreshusername')
|
|
||||||
|
|
||||||
user.value.discord.username = response.username
|
|
||||||
|
|
||||||
useToast().success('Gebruikersnaam is ververst')
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
|
|
||||||
useToast().error(e.statusMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeWhitelist = async () => {
|
|
||||||
try {
|
|
||||||
await $fetch('/api/minecraft/removewhitelist')
|
|
||||||
|
|
||||||
user.value.minecraft.uuid = null
|
|
||||||
user.value.minecraft.username = null
|
|
||||||
|
|
||||||
useToast().success('Minecraft is niet meer gekoppeld')
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
useToast().error(e.statusMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const setDefaultUsername = async (type) => {
|
|
||||||
try {
|
|
||||||
const response = await $fetch('/api/auth/user/setdefaultusername', {
|
|
||||||
method: 'POST',
|
|
||||||
body: { type: type }
|
|
||||||
})
|
|
||||||
|
|
||||||
user.value.username = response.username
|
|
||||||
user.value.useMinecraftUsername = type === 'discord' ? false : true
|
|
||||||
|
|
||||||
useToast().success(`${ type === 'discord' ? 'Discord' : 'Minecraft' } is nu de standaard gebruikersnaam`)
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
useToast().error(e.statusMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,26 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex h-screen w-full flex-col items-center justify-center bg-transparent px-2 text-primary">
|
|
||||||
<h1 class="mb-5 text-center text-3xl font-bold sm:text-5xl">Polarcraft S5</h1>
|
|
||||||
<p class="mb-10 max-w-xl text-sm sm:text-base">
|
|
||||||
<b>Welkom bij Polarcraft seizoen 5!</b> Start door in te loggen met Discord en vervolgens je account te koppelen met Minecraft. Als je problemen hebt maak dan in Discord een ticket aan door het <span class="highlight">/ticket</span> commando uit te voeren.
|
|
||||||
</p>
|
|
||||||
<Button @click="navigateTo(config.public.redirectUrl, { external: true })">
|
|
||||||
Log in with Discord
|
|
||||||
<Icon size="1.6em" name="ic:baseline-discord" />
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<div class="absolute left-0 top-0 -z-10 h-screen w-full overflow-hidden">
|
|
||||||
<nuxt-img src="/pictures/diamond_wall.webp" class="h-full w-full scale-105 object-cover blur-[1px] brightness-75" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
definePageMeta({
|
|
||||||
layout: 'blank'
|
|
||||||
})
|
|
||||||
|
|
||||||
useHead({ title: 'Login | Polarcraft' })
|
|
||||||
|
|
||||||
const config = useRuntimeConfig()
|
|
||||||
</script>
|
|
@ -1,13 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="-mx-10 h-full">
|
|
||||||
<iframe src="https://squaremap-demo.jpenilla.xyz/" class="h-full w-full" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
definePageMeta({
|
|
||||||
middleware: ["auth"]
|
|
||||||
})
|
|
||||||
|
|
||||||
useHead({ title: 'Map | Polarcraft' })
|
|
||||||
</script>
|
|
@ -1,14 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="mt-5">
|
|
||||||
Server Control
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
definePageMeta({
|
|
||||||
middleware: ["auth"],
|
|
||||||
moderator: true
|
|
||||||
})
|
|
||||||
|
|
||||||
useHead({ title: 'Server Control | Polarcraft' })
|
|
||||||
</script>
|
|
@ -1,78 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="mt-5">
|
|
||||||
<Modal v-if="actionModal.open" :title="actionModal.title" @click="actionModal.func">
|
|
||||||
<Select v-model="actionModal.reason" :options="[{ name: 'Griefing', value: 'griefing' }]">Reason</Select>
|
|
||||||
</Modal>
|
|
||||||
<h1 class="text-2xl font-bold text-primary">
|
|
||||||
Users
|
|
||||||
</h1>
|
|
||||||
<div class="mx-auto my-10 max-w-4xl">
|
|
||||||
<h2 class="mb-2 text-xl font-bold text-primary">User Actions</h2>
|
|
||||||
<div class="w-full rounded border-[1px] border-primary px-3 py-1 text-left">
|
|
||||||
<table class="w-full table-auto divide-y divide-neutral-500 rounded">
|
|
||||||
<tr class="border-b-2 border-primary text-gray-200">
|
|
||||||
<th class="py-1">Username</th>
|
|
||||||
<th class="py-1">Team</th>
|
|
||||||
<th class="py-1">MC Linked</th>
|
|
||||||
<th class="py-1">Actions</th>
|
|
||||||
</tr>
|
|
||||||
<tr v-for="user in users" :key="user._id" class="text-gray-200">
|
|
||||||
<td class="py-1">{{ user.username }}</td>
|
|
||||||
<td class="py-1">{{ user.team ? teams.filter(a => a._id === user.team.id)[0].name : '-' }}</td>
|
|
||||||
<td class="py-1">{{ user.minecraft.uuid ? 'True ' : 'False' }}</td>
|
|
||||||
<td class="space-x-2 py-1">
|
|
||||||
<span v-if="currentUser.role.admin" class="rounded bg-red-700 px-2 text-red-300 hover:cursor-pointer hover:bg-red-800" @click="openModal(user, 'ban', ban(user))">Ban</span>
|
|
||||||
<span class="rounded bg-orange-700 px-2 text-orange-300 hover:cursor-pointer hover:bg-orange-800">Suspend</span>
|
|
||||||
<span class="rounded bg-yellow-700 px-2 text-yellow-300 hover:cursor-pointer hover:bg-yellow-800">Warn</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
definePageMeta({
|
|
||||||
middleware: ["auth"],
|
|
||||||
moderator: true
|
|
||||||
})
|
|
||||||
|
|
||||||
useHead({ title: 'Users | Polarcraft' })
|
|
||||||
|
|
||||||
const currentUser = useState('user')
|
|
||||||
|
|
||||||
const { data: users } = useFetch('/api/auth/getusers')
|
|
||||||
const { data: teams } = useFetch('/api/team/all')
|
|
||||||
|
|
||||||
const actionModal = ref({
|
|
||||||
open: false,
|
|
||||||
title: '',
|
|
||||||
reason: '',
|
|
||||||
userId: '',
|
|
||||||
func: null,
|
|
||||||
})
|
|
||||||
|
|
||||||
const openModal = (user, type, func) => {
|
|
||||||
if (type === 'ban') actionModal.value.title = 'Ban User'
|
|
||||||
if (type === 'suspend') actionModal.value.title = 'Suspend User'
|
|
||||||
if (type === 'warn') actionModal.value.title = 'Warn User'
|
|
||||||
|
|
||||||
actionModal.value.func = func
|
|
||||||
actionModal.value.userId = user._id;
|
|
||||||
actionModal.value.open = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const ban = async (user) => {
|
|
||||||
try {
|
|
||||||
const response = await $fetch(`/api/auth/user/${user._id}/ban`, {
|
|
||||||
reason: "reason"
|
|
||||||
})
|
|
||||||
|
|
||||||
user = response;
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
useToast().error('Error banning user')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,158 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="mt-5 text-primary">
|
|
||||||
<Modal
|
|
||||||
v-if="storeModal.open"
|
|
||||||
title="Create new store"
|
|
||||||
:delete="!storeModal.new"
|
|
||||||
@submit="submitModal"
|
|
||||||
@delete="deleteStore"
|
|
||||||
@close="storeModal.open = false"
|
|
||||||
>
|
|
||||||
<Input v-model="storeModal.name" background-class="bg-neutral-800">Store name</Input>
|
|
||||||
<div class="flex gap-x-5">
|
|
||||||
<Input v-model="storeModal.coords.x" background-class="bg-neutral-800">Coordinate X</Input>
|
|
||||||
<Input v-model="storeModal.coords.z" background-class="bg-neutral-800">Coordinate Z</Input>
|
|
||||||
</div>
|
|
||||||
<h2 class="font-bold">Items</h2>
|
|
||||||
<div class="max-h-56 space-y-3 overflow-scroll">
|
|
||||||
<div v-for="item, index in storeModal.items" :key="item.name" class="flex gap-2">
|
|
||||||
<Input v-model="item.displayName" background-class="bg-neutral-800">Name</Input>
|
|
||||||
<Input v-model="item.price" background-class="bg-neutral-800">Price per quantity</Input>
|
|
||||||
<Button type="danger" @click="storeModal.items.splice(index, 1)"><Icon size="1.5em" name="ph:trash" class="-my-2" /></Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Button class="" @click="storeModal.items.push({ displayName: '', price: '' })">Add Item</Button>
|
|
||||||
</Modal>
|
|
||||||
<h1 class="pb-10 text-2xl font-bold text-primary">
|
|
||||||
Player Stores
|
|
||||||
</h1>
|
|
||||||
<div class="fixed bottom-10 right-10">
|
|
||||||
<Button class="fixed" @click="openStoreModal(true)">
|
|
||||||
Create Store
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-wrap gap-10">
|
|
||||||
<div v-for="store in stores" :key="store._id.toString()" class="h-min w-96 rounded-lg bg-neutral-800 text-gray-200 shadow relative">
|
|
||||||
<div v-if="store.ownerId === user._id" class="absolute right-5 top-5 hover:cursor-pointer" @click="openStoreModal(false, store)">
|
|
||||||
<Icon name="ph:pencil-simple" size="1.6em" />
|
|
||||||
</div>
|
|
||||||
<div class="p-5 pb-3">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<img :src="'https://api.mineatar.io/face/' + store.owner.minecraft.uuid + '?scale=3'" class="mr-2 aspect-square rounded">
|
|
||||||
{{ store.owner.username }}
|
|
||||||
</div>
|
|
||||||
<h2 class="text-2xl font-bold text-primary">
|
|
||||||
{{ store.name }}
|
|
||||||
<span class="text-sm font-medium">({{ store.coords.x }}, {{ store.coords.z }})</span>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<h2 class="ml-3 text-xl font-bold">Items</h2>
|
|
||||||
<div class="divide-y divide-neutral-600 rounded-lg bg-neutral-700 px-4 pt-2 pb-2">
|
|
||||||
<div v-for="item in store.items" :key="item.displayname" class="py-1">
|
|
||||||
<b>{{ item.displayName }}</b> for <b>{{ item.price }}</b>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
definePageMeta({
|
|
||||||
middleware: ["auth"]
|
|
||||||
})
|
|
||||||
|
|
||||||
const user = useState('user')
|
|
||||||
|
|
||||||
useHead({ title: 'Player Stores | Polarcraft' })
|
|
||||||
|
|
||||||
const { data: stores } = await useFetch('/api/store');
|
|
||||||
|
|
||||||
const storeModal = ref({
|
|
||||||
open: false,
|
|
||||||
name: '',
|
|
||||||
new: false,
|
|
||||||
id: '',
|
|
||||||
coords: {
|
|
||||||
x: '',
|
|
||||||
z: '',
|
|
||||||
},
|
|
||||||
items: [
|
|
||||||
{ displayName: '', price: '' }
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
const openStoreModal = (newStore, store) => {
|
|
||||||
storeModal.value.open = true;
|
|
||||||
|
|
||||||
if (newStore === true) {
|
|
||||||
storeModal.value.name = '';
|
|
||||||
storeModal.value.items = [
|
|
||||||
{ displayName: '', price: '' }
|
|
||||||
];
|
|
||||||
|
|
||||||
storeModal.value.coords = { x: '', z: '' }
|
|
||||||
|
|
||||||
storeModal.value.new = true
|
|
||||||
} else {
|
|
||||||
storeModal.value.new = false
|
|
||||||
|
|
||||||
storeModal.value.items = []
|
|
||||||
store.items.forEach(item => {
|
|
||||||
storeModal.value.items.push(item);
|
|
||||||
})
|
|
||||||
|
|
||||||
storeModal.value.name = store.name
|
|
||||||
storeModal.value.id = store._id
|
|
||||||
storeModal.value.coords.x = store.coords.x
|
|
||||||
storeModal.value.coords.z = store.coords.z
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const submitModal = async () => {
|
|
||||||
try {
|
|
||||||
const store = await $fetch('/api/store', {
|
|
||||||
method: 'POST',
|
|
||||||
body: { name: storeModal.value.name, coords: storeModal.value.coords, items: storeModal.value.items, id: storeModal.value.id || null }
|
|
||||||
})
|
|
||||||
|
|
||||||
if (storeModal.value.id) {
|
|
||||||
const oldStore = stores.value.filter(a => a._id === storeModal.value.id)[0]
|
|
||||||
|
|
||||||
oldStore.name = storeModal.value.name;
|
|
||||||
oldStore.coords = storeModal.value.coords;
|
|
||||||
oldStore.items = storeModal.value.items;
|
|
||||||
} else {
|
|
||||||
store.owner = user
|
|
||||||
stores.value.push(store)
|
|
||||||
}
|
|
||||||
|
|
||||||
storeModal.value.open = false
|
|
||||||
|
|
||||||
useToast().success('Succesvol store aangemaakt')
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
useToast().error(e.statusMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteStore = async () => {
|
|
||||||
try {
|
|
||||||
await $fetch('/api/store', {
|
|
||||||
method: 'DELETE',
|
|
||||||
body: { id: storeModal.value.id }
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
stores.value = stores.value.filter(a => a._id !== storeModal.value.id)
|
|
||||||
|
|
||||||
storeModal.value.open = false
|
|
||||||
useToast().success('Succesvol store verwijderd')
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
useToast().error(e.statusMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,19 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="mt-5">
|
|
||||||
<h1 class="text-2xl font-bold text-primary">
|
|
||||||
Team
|
|
||||||
</h1>
|
|
||||||
<TeamNone v-if="!user.team" />
|
|
||||||
<TeamDefault v-else />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
const user = useState('user')
|
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
middleware: ["auth"]
|
|
||||||
})
|
|
||||||
|
|
||||||
useHead({ title: 'Team | Polarcraft' })
|
|
||||||
</script>
|
|
Before Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 174 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 825 B |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 103 KiB |
@ -1 +0,0 @@
|
|||||||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
|
@ -1,13 +0,0 @@
|
|||||||
export default defineEventHandler(async (event) => {
|
|
||||||
const user = await getAuth(event)
|
|
||||||
|
|
||||||
if (!user.role.moderator) return createError({ statusCode: 403, statusMessage: 'Gebruiker heeft geen toegang tot alle gebruikers' })
|
|
||||||
|
|
||||||
const usersColl = db.collection('users');
|
|
||||||
|
|
||||||
const cursor = usersColl.find()
|
|
||||||
|
|
||||||
const users = await cursor.toArray()
|
|
||||||
|
|
||||||
return users
|
|
||||||
});
|
|
@ -1,52 +0,0 @@
|
|||||||
export default defineEventHandler(async (event) => {
|
|
||||||
const { code } = getQuery(event)
|
|
||||||
|
|
||||||
const config = useRuntimeConfig()
|
|
||||||
|
|
||||||
if (!code) return sendRedirect(event, '/', 302)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const tokenResponseData = await $fetch('https://discord.com/api/oauth2/token', {
|
|
||||||
method: 'POST',
|
|
||||||
body: new URLSearchParams({
|
|
||||||
client_id: config.discordId,
|
|
||||||
client_secret: config.discordSecret,
|
|
||||||
code: code,
|
|
||||||
grant_type: 'authorization_code',
|
|
||||||
redirect_uri: config.redirectURI,
|
|
||||||
scope: 'identify',
|
|
||||||
}).toString(),
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const userResult = await $fetch('https://discord.com/api/users/@me', {
|
|
||||||
headers: {
|
|
||||||
authorization: `Bearer ${tokenResponseData.access_token}`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const coll = db.collection('users')
|
|
||||||
|
|
||||||
const doc = {
|
|
||||||
discord: {
|
|
||||||
id: userResult.id,
|
|
||||||
username: userResult.username,
|
|
||||||
avatarHash: userResult.avatar || null
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
await coll.updateOne({ 'discord.id': userResult.id }, { $set: doc, $setOnInsert: { minecraft: { uuid: null, username: null }, teamInvites: [], role: {} } }, { upsert: true })
|
|
||||||
|
|
||||||
const token = createToken(tokenResponseData.access_token, tokenResponseData.refresh_token, tokenResponseData.expires_in, userResult.id )
|
|
||||||
|
|
||||||
setCookie(event, 'jwt', token, { httpOnly: true, maxAge: tokenResponseData.expires_in * 1000 })
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
|
|
||||||
throw createError({ statusCode: 500, statusMessage: 'Error tijdens het genereren van JWT token'})
|
|
||||||
}
|
|
||||||
|
|
||||||
return sendRedirect(event, '/', 302)
|
|
||||||
});
|
|
@ -1,25 +0,0 @@
|
|||||||
import { ObjectId } from "mongodb";
|
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
|
||||||
const { reason } = await readBody(event)
|
|
||||||
|
|
||||||
const currentUser = await getAuth(event)
|
|
||||||
const userId = event.context.params.id;
|
|
||||||
|
|
||||||
if (!reason) return createError({ statusCode: 400, statusMessage: 'Reason is vereist' })
|
|
||||||
if (!currentUser.role.admin) return createError({ statusCode: 403, statusMessage: 'Geen toegang om gebruiker te bannen' })
|
|
||||||
|
|
||||||
const usersColl = db.collection('users')
|
|
||||||
const user = usersColl.findOneAndUpdate({ _id: new ObjectId(userId)}, { $set: { banned: { reason: reason, date: new Date() } } })
|
|
||||||
|
|
||||||
if (!user.value) return createError({ statusCode: 500, statusMessage: 'Error tijdens het updaten van de gebruiker' })
|
|
||||||
|
|
||||||
await $fetch(config.discordHost + '/user/ban', {
|
|
||||||
method: 'POST',
|
|
||||||
body: { reason: reason, discordId: user.discord.id }
|
|
||||||
})
|
|
||||||
|
|
||||||
await sendRconCommand(`ban ${user.value.minecraft.uuid} ${reason}`)
|
|
||||||
|
|
||||||
return user
|
|
||||||
});
|
|
@ -1,5 +0,0 @@
|
|||||||
export default defineEventHandler(async (event) => {
|
|
||||||
const user = await getAuth(event)
|
|
||||||
|
|
||||||
return user
|
|
||||||
});
|
|
@ -1,16 +0,0 @@
|
|||||||
import { ObjectId } from "mongodb";
|
|
||||||
import { applyUsername } from "~/server/utils/auth";
|
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
|
||||||
const { type } = await readBody(event)
|
|
||||||
const auth = await getAuth(event)
|
|
||||||
|
|
||||||
const usersColl = db.collection('users')
|
|
||||||
|
|
||||||
const username = type === 'discord' ? auth.discord.username : auth.minecraft.username
|
|
||||||
|
|
||||||
const newUser = await usersColl.findOneAndUpdate({ _id: new ObjectId(auth._id) }, { $set: { username: username, useMinecraftUsername: type === 'discord' ? false : true } }, { returnDocument: 'after' })
|
|
||||||
await applyUsername(newUser.value)
|
|
||||||
|
|
||||||
return newUser.value
|
|
||||||
});
|
|