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
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2021
|
||||
"ecmaVersion": 2021,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"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 { simpleEmbed } = require('../functions/embeds.js');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
@ -10,15 +11,13 @@ module.exports = {
|
||||
.setDescription('The amount of messages to clear')
|
||||
.setRequired(true)),
|
||||
|
||||
async execute({ interaction, createEmbed, client }) {
|
||||
async execute(interaction) {
|
||||
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: [createEmbed.basic(`Cleared **${amount}** messages`)], ephemeral: true });
|
||||
await interaction.reply({ embeds: [simpleEmbed(`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 { Player } = require('discord-player');
|
||||
const express = require('express');
|
||||
const Sequelize = require('sequelize');
|
||||
const dotenv = require('dotenv');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const dotenv = require('dotenv');
|
||||
const express = require('express');
|
||||
|
||||
const createEmbed = require('./functions/createEmbed.js');
|
||||
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
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent] });
|
||||
client.player = new Player(client);
|
||||
|
||||
require('./functions/player.js').registerEvents({ client, createEmbed });
|
||||
|
||||
|
||||
// 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');
|
||||
// Configure database
|
||||
const sequelize = new Sequelize('polarcraft', 'user', process.env.DATABSE_PASSWORD, {
|
||||
host: 'localhost',
|
||||
dialect: 'sqlite',
|
||||
logging: false,
|
||||
// SQLite only
|
||||
storage: 'database.sqlite',
|
||||
});
|
||||
|
||||
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();
|
||||
|
||||
const commandsPath = path.join(__dirname, 'commands');
|
||||
@ -52,12 +36,10 @@ for (const file of commandFiles) {
|
||||
if ('data' in command && 'execute' in command) {
|
||||
client.commands.set(command.data.name, command);
|
||||
} 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 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 event = require(filePath);
|
||||
if (event.once) {
|
||||
client.once(event.name, (...args) => event.execute({ client, log, createEmbed }, ...args));
|
||||
client.once(event.name, (...args) => event.execute(...args));
|
||||
} 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);
|
||||
|
||||
module.exports.client = client;
|
5227
discordbot/package-lock.json
generated
@ -2,21 +2,20 @@
|
||||
"name": "discordbot",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.ts",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "tsx watch src/index.ts",
|
||||
"start": "node dist/index.js",
|
||||
"build": "tsup src/index.ts --minify"
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"author": "Xeovalyte",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"discord.js": "^14.11.0",
|
||||
"dotenv": "^16.3.1"
|
||||
"discord.js": "^14.12.1",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"sequelize": "^6.32.1",
|
||||
"sqlite3": "^5.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsup": "^7.1.0",
|
||||
"tsx": "^3.12.7",
|
||||
"typescript": "^5.1.3"
|
||||
"eslint": "^8.46.0"
|
||||
}
|
||||
}
|
||||
|
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
|
||||
});
|