initialized discord bot

This commit is contained in:
Xeovalyte 2023-08-02 13:26:48 +02:00
parent fbc9238e97
commit 0c1cc63b9b
163 changed files with 183 additions and 61501 deletions

View File

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 663 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 601 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 899 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 984 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -1,49 +0,0 @@
{
"extends": "eslint:recommended",
"env": {
"node": true,
"es6": true
},
"parserOptions": {
"ecmaVersion": 2021
},
"rules": {
"arrow-spacing": ["warn", { "before": true, "after": true }],
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
"comma-dangle": ["error", "always-multiline"],
"comma-spacing": "error",
"comma-style": "error",
"curly": ["error", "multi-line", "consistent"],
"dot-location": ["error", "property"],
"handle-callback-err": "off",
"indent": ["error", 2],
"keyword-spacing": "error",
"max-nested-callbacks": ["error", { "max": 4 }],
"max-statements-per-line": ["error", { "max": 2 }],
"no-console": "off",
"no-empty-function": "error",
"no-floating-decimal": "error",
"no-inline-comments": "error",
"no-lonely-if": "error",
"no-multi-spaces": "error",
"no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF": 0 }],
"no-shadow": ["error", { "allow": ["err", "resolve", "reject"] }],
"no-trailing-spaces": ["error"],
"no-var": "error",
"object-curly-spacing": ["error", "always"],
"prefer-const": "error",
"quotes": ["error", "single"],
"semi": ["error", "always"],
"space-before-blocks": "error",
"space-before-function-paren": ["error", {
"anonymous": "never",
"named": "never",
"asyncArrow": "always"
}],
"space-in-parens": "error",
"space-infix-ops": "error",
"space-unary-ops": "error",
"spaced-comment": "error",
"yoda": "error"
}
}

View File

@ -1,2 +0,0 @@
.env
node_modules

View File

@ -1,11 +0,0 @@
FROM node:18-alpine
WORKDIR /usr/src/app
COPY . .
RUN npm install
EXPOSE 3000
CMD [ "node", "index.js" ]

View File

@ -1,24 +0,0 @@
const { SlashCommandBuilder, PermissionFlagsBits } = require('discord.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('clear')
.setDescription('Clear 1-100 messages')
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
.addNumberOption(option => option
.setName('amount')
.setDescription('The amount of messages to clear')
.setRequired(true)),
async execute({ interaction, createEmbed, client }) {
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')] });
const channel = client.channels.cache.get(interaction.channelId);
channel.bulkDelete(amount);
await interaction.reply({ embeds: [createEmbed.basic(`Cleared **${amount}** messages`)], ephemeral: true });
},
};

View File

@ -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')] });
}
},
};

View File

@ -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 });
},
};

View File

@ -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: [] });
});
}
},
};

View File

@ -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,
}),
});
}
},
};

View File

@ -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] });
},
};

View File

@ -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] });
},
};

View File

@ -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 });
}
}
},
};

View File

@ -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);
},
};

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -1,76 +0,0 @@
const chalk = require('chalk');
const { Client, GatewayIntentBits, Collection } = require('discord.js');
const { Player } = require('discord-player');
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');
});
// Command handling
client.commands = new Collection();
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) {
client.commands.set(command.data.name, command);
} else {
log.Warn(`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'));
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));
} else {
client.on(event.name, (...args) => event.execute({ client, log, createEmbed }, ...args));
}
}
client.login(process.env.DISCORD_TOKEN);
module.exports.client = client;

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -1,17 +1,49 @@
{
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"root": true,
"extends": "eslint:recommended",
"env": {
"node": true,
"es6": true
},
"parserOptions": {
"ecmaVersion": 2021,
"sourceType": "module"
"ecmaVersion": 2021
},
"rules": {
"no-console": "off"
"arrow-spacing": ["warn", { "before": true, "after": true }],
"brace-style": ["error", "stroustrup", { "allowSingleLine": true }],
"comma-dangle": ["error", "always-multiline"],
"comma-spacing": "error",
"comma-style": "error",
"curly": ["error", "multi-line", "consistent"],
"dot-location": ["error", "property"],
"handle-callback-err": "off",
"indent": ["error", "tab"],
"keyword-spacing": "error",
"max-nested-callbacks": ["error", { "max": 4 }],
"max-statements-per-line": ["error", { "max": 2 }],
"no-console": "off",
"no-empty-function": "error",
"no-floating-decimal": "error",
"no-inline-comments": "error",
"no-lonely-if": "error",
"no-multi-spaces": "error",
"no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF": 0 }],
"no-shadow": ["error", { "allow": ["err", "resolve", "reject"] }],
"no-trailing-spaces": ["error"],
"no-var": "error",
"object-curly-spacing": ["error", "always"],
"prefer-const": "error",
"quotes": ["error", "single"],
"semi": ["error", "always"],
"space-before-blocks": "error",
"space-before-function-paren": ["error", {
"anonymous": "never",
"named": "never",
"asyncArrow": "always"
}],
"space-in-parens": "error",
"space-infix-ops": "error",
"space-unary-ops": "error",
"spaced-comment": "error",
"yoda": "error"
}
}

12
discordbot/index.js Normal file
View File

@ -0,0 +1,12 @@
const { Client, Events, GatewayIntentBits } = require('discord.js');
const dotenv = require('dotenv');
dotenv.config();
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.once(Events.ClientReady, c => {
console.log(`Ready! Logged in as ${c.user.tag}`);
});
client.login(process.env.DISCORD_TOKEN);

File diff suppressed because it is too large Load Diff

View File

@ -2,26 +2,17 @@
"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",
"lint": "eslint \"**/*.{ts, tsx}\"",
"eslint-fix": "eslint . --fix"
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"author": "Xeovalyte",
"license": "ISC",
"dependencies": {
"discord.js": "^14.11.0",
"discord.js": "^14.12.1",
"dotenv": "^16.3.1"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.60.1",
"@typescript-eslint/parser": "^5.60.1",
"eslint": "^8.43.0",
"tsup": "^7.1.0",
"tsx": "^3.12.7",
"typescript": "^5.1.3"
"eslint": "^8.46.0"
}
}

View File

@ -1,24 +0,0 @@
import { ChatInputCommandInteraction, SlashCommandBuilder, Client, PermissionFlagsBits } from "discord.js";
import { basicEmbed } from '../createEmbed'
export const data = new SlashCommandBuilder()
.setName("clear")
.setDescription("Clear 1-100 messages")
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
.addNumberOption(option => option
.setName('amount')
.setDescription('The amount of messages to creat')
.setRequired(true)
)
export async function execute({ interaction }: { interaction: ChatInputCommandInteraction, client: Client }) {
const amount = interaction.options.getNumber('amount');
if (!amount || !interaction.channel || amount < 1 || amount > 100) return await interaction.reply({ embeds: [basicEmbed('The amount must be between 1-100')] });
if (interaction.channel.isDMBased()) return await interaction.reply({ embeds: [basicEmbed('This command can only be executed inside a guild')], ephemeral: true })
interaction.channel.bulkDelete(amount)
await interaction.reply({ embeds: [basicEmbed(`Cleared **${amount}** messages`)], ephemeral: true })
}

View File

@ -1,11 +0,0 @@
import { ChatInputCommandInteraction, SlashCommandBuilder, Client } from "discord.js";
import { basicEmbed } from '../createEmbed'
export const data = new SlashCommandBuilder()
.setName("ping")
.setDescription("Replies with Pong!");
export async function execute({ interaction, client }: { interaction: ChatInputCommandInteraction, client: Client }) {
const reply = await interaction.reply({ embeds: [basicEmbed(`Websocket heartbeat: **${client.ws.ping}ms**\n Roundtrip latency: **Pinging...**`)], fetchReply: true, ephemeral: true });
interaction.editReply({ embeds: [basicEmbed(`Websocket heartbeat: **${client.ws.ping}ms**\n Roundtrip latency: **${reply.createdTimestamp - interaction.createdTimestamp}ms**`)] });
}

View File

@ -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
};

View File

@ -1,8 +0,0 @@
import { EmbedBuilder } from 'discord.js'
import { config } from './config'
export const basicEmbed = (description: string) => {
return new EmbedBuilder()
.setColor(config.EMBED_COLOR)
.setDescription(description);
};

View File

@ -1,23 +0,0 @@
import { Events, Client, BaseInteraction } from 'discord.js'
export const name = Events.InteractionCreate
export const execute = async ({ client }: { client: Client }, 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 });
} catch (error) {
console.error(error);
await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
}
}
}

View File

@ -1,14 +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 }) => {
if (!client.user) throw Error('No client.user')
console.log(`Ready! Logged in as ${client.user.tag}`)
registerCommands()
}

View File

@ -1,48 +0,0 @@
import { Client, GatewayIntentBits, Collection } from 'discord.js'
import fs from 'node:fs'
import path from 'node:path'
import { config } from './config'
type Command = {
data: any
}
const client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent]
})
client.commands = new Collection()
const commandsPath = path.join(__dirname, 'commands');
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.ts'));
(async () => {
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) {
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'));
(async () => {
for (const file of eventFiles) {
const filePath = path.join(eventsPath, file);
const event = await import(filePath);
if (event.once) {
client.once(event.name, (...args) => event.execute({ client }, ...args));
} else {
client.on(event.name, (...args) => event.execute({ client }, ...args));
}
}
})
client.login(config.DISCORD_TOKEN);

View File

@ -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);
}
}

View File

@ -1,18 +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,
"compilerOptions": {
"module": "commonjs"
}
}
}

11
discordbot/types.d.ts vendored
View File

@ -1,11 +0,0 @@
import { Collection } from 'discord.js'
declare module 'discord.js' {
export interface Client {
commands: Collection<unknown, any>
}
}
declare global {
type CreateEmbed = (message: string) => void
}

View File

@ -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
View File

@ -1,9 +0,0 @@
node_modules
*.log*
.nuxt
.nitro
.cache
.output
.env
dist
.DS_Store

View File

@ -1,2 +0,0 @@
shamefully-hoist=true
strict-peer-dependencies=false

View File

@ -1,9 +0,0 @@
FROM node:18-alpine
WORKDIR /usr/src/app
COPY .output .
EXPOSE 3000
CMD [ "node", "server/index.mjs" ]

View File

@ -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.

View File

@ -1,5 +0,0 @@
<template>
<div class="h-screen w-full overflow-y-auto">
<NuxtLayout />
</div>
</template>

View File

@ -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%)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 MiB

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -1,3 +0,0 @@
<template>
<NuxtPage />
</template>

View File

@ -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>

View File

@ -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')
}
})

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 825 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

View File

@ -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"}

View File

@ -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
});

View File

@ -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)
});

View File

@ -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
});

View File

@ -1,5 +0,0 @@
export default defineEventHandler(async (event) => {
const user = await getAuth(event)
return user
});

View File

@ -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
});

View File

@ -1,26 +0,0 @@
export default defineEventHandler(async (event) => {
const user = await getAuth(event)
const userResult = await $fetch('https://discord.com/api/users/@me', {
headers: {
authorization: `Bearer ${user.accessToken}`
}
})
const coll = db.collection('users')
const doc = {
discord: {
id: userResult.id,
username: userResult.username,
avatarHash: userResult.avatar || null
},
username: user.useMinecraftUsername ? user.minecraft.username : userResult.username
}
const newUser = await coll.findOneAndUpdate({ 'discord.id': userResult.id }, { $set: doc }, { returnDocument: 'after' })
applyUsername(newUser)
return doc.discord
});

View File

@ -1,22 +0,0 @@
export default defineEventHandler(async (event) => {
const { content, uuid } = await readBody(event);
const config = useRuntimeConfig();
const usersColl = db.collection('users')
const user = await usersColl.findOne({ 'minecraft.uuid': uuid })
const response = await getUsernameWithTeam(user);
await $fetch(config.discordHost + '/minecraft/sendchatmessage', {
method: 'POST',
body: {
username: response.usernameWithoutStyle,
// avatarURL: 'https://cdn.discordapp.com/avatars/' + doc.discord.id + '/' + doc.discord.avatarHash + '.png',
avatarURL: 'https://api.mineatar.io/face/' + user.minecraft.uuid + '?scale=16',
content: content,
}
})
return { code: 'success' }
});

View File

@ -1,21 +0,0 @@
import { getUsernameWithTeam } from "~/server/utils/auth";
export default defineEventHandler(async (event) => {
const { discordId, content } = await readBody(event);
const coll = db.collection('users');
const user = await coll.findOne({ 'discord.id': discordId });
const response = await getUsernameWithTeam(user)
let tellraw;
if (user.team) {
tellraw = `["",{"text":"DC","color":"gray"},{"text":" ${user.username} ["},{"text":"${response.team.name}","color":"${response.team.color}"},{"text":"] > ${content}"}]`
} else {
tellraw = `["",{"text":"DC","color":"gray"},{"text":" ${user.username} > ${content}"}]`
}
await sendRconCommand(`tellraw @a ${tellraw}`)
return { status: 'success' }
});

View File

@ -1,18 +0,0 @@
export default defineEventHandler(async (event) => {
const { content, uuid } = await readBody(event);
const config = useRuntimeConfig();
const coll = db.collection('users')
const doc = await coll.findOne({ 'minecraft.uuid': uuid })
await $fetch(config.discordHost + '/minecraft/sendgamemessage', {
method: 'POST',
body: {
avatarURL: 'https://api.mineatar.io/face/' + doc.minecraft.uuid + '?scale=16',
content: content,
}
})
return { status: 'success' }
});

View File

@ -1,14 +0,0 @@
export default defineEventHandler(async (event) => {
const user = await getAuth(event)
if (!auth.minecraft.uuid) throw createError({ errorCode: 400, statusMessage: 'Geen Minecraft account is gekoppeld' })
const minecraftProfile = await $fetch(`https://sessionserver.mojang.com/session/minecraft/profile/${user.minecraft.uuid}`)
const usersColl = db.collection('users')
const newUser = await usersColl.findOneAndUpdate({ 'minecraft.uuid': auth.minecraft.uuid }, { $set: { 'minecraft.username': minecraftProfile.name, username: user.useMinecraftUsername ? minecraftProfile.name : user.discord.username } }, { returnDocument: 'after' })
applyUsername(newUser.value)
return newUser.value
});

View File

@ -1,13 +0,0 @@
export default defineEventHandler(async (event) => {
const auth = await getAuth(event)
if (!auth.minecraft.uuid) throw createError({ errorCode: 400, statusMessage: 'Geen Minecraft account is gekoppeld' })
const whitelistColl = db.collection('whitelist')
await whitelistColl.deleteOne({ uuid: auth.minecraft.uuid })
const usersColl = db.collection('users')
await usersColl.findOneAndUpdate({ 'minecraft.uuid': auth.minecraft.uuid }, { $set: { 'minecraft.uuid': null, 'minecraft.username': null } })
return { code: 'success' }
});

View File

@ -1,38 +0,0 @@
import { getUsernameWithTeam } from "~/server/utils/auth";
export default defineEventHandler(async (event) => {
const { uuid } = await readBody(event)
const coll = db.collection('whitelist')
const usersColl = db.collection('users')
const doc = await coll.findOne({ uuid: uuid })
if (doc && !doc.verified) return { code: doc.code, verified: false }
if (doc && doc.verified) {
const user = await usersColl.findOne({ 'minecraft.uuid': uuid });
const response = await getUsernameWithTeam(user);
return { verified: true, username: response.username, usernameWithoutStyle: response.usernameWithoutStyle }
}
await coll.createIndex({ code: 1 }, { unique: true })
const code = await insertDoc(coll, uuid)
return { code: code.toString(), verified: false }
});
const insertDoc = async (coll, uuid) => {
try {
const code = Math.floor(100000 + Math.random() * 900000)
await coll.insertOne({ uuid: uuid, verified: false, code: code.toString() })
return code;
} catch (e) {
const code = await insertDoc(coll, uuid)
return code;
}
}

View File

@ -1,23 +0,0 @@
export default defineEventHandler(async (event) => {
const { code } = await readBody(event)
if (!code) throw createError({ statusCode: 400, statusMessage: 'Code is vereist'})
const auth = await getAuth(event)
const whitelistColl = db.collection('whitelist')
const whitelistDoc = await whitelistColl.findOne({ code: code.toString() })
if (!whitelistDoc) throw createError({ statusCode: 400, statusMessage: 'Code is niet gevonden, join eerste de Minecraft server' })
if (whitelistDoc && whitelistDoc.verified) throw createError({ statusCode: 400, statusMessage: 'Already verified' })
await whitelistColl.updateOne({ code: code.toString() }, { $set: { verified: true } })
const minecraftProfile = await $fetch(`https://sessionserver.mojang.com/session/minecraft/profile/${whitelistDoc.uuid}`)
const usersColl = db.collection('users')
await usersColl.updateOne({ 'discord.id': auth.discord.id }, { $set: { 'minecraft.uuid': whitelistDoc.uuid, 'minecraft.username': minecraftProfile.name } })
return { uuid: whitelistDoc.uuid, verified: false, username: minecraftProfile.name }
});

View File

@ -1,17 +0,0 @@
import { ObjectId } from "mongodb";
export default defineEventHandler(async (event) => {
const { id } = await readBody(event)
const user = await getAuth(event)
const storesColl = db.collection('stores');
const result = await storesColl.deleteOne({ _id: new ObjectId(id) })
if (result.deletedCount === 1) {
return result
} else {
throw createError({ statusCode: 500, statusMessage: 'Error tijdens het verwijderen van de store' })
}
});

View File

@ -1,21 +0,0 @@
export default defineEventHandler(async () => {
const storesColl = db.collection('stores');
const cursor = storesColl.aggregate([
{
$lookup:
{
from: "users",
localField: "ownerId",
foreignField: "_id",
as: "owner",
},
},
{
$unwind: "$owner",
},
])
return await cursor.toArray()
});

View File

@ -1,13 +0,0 @@
import { ObjectId } from "mongodb";
export default defineEventHandler(async (event) => {
const { coords, name, items, id } = await readBody(event)
const user = await getAuth(event)
const storesColl = db.collection('stores');
const { value: store } = await storesColl.findOneAndUpdate({ _id: id ? new ObjectId(id) : new ObjectId() }, { $set: { coords: coords, name: name, items: items, ownerId: user._id, } }, { returnDocument: 'after', upsert: true })
return store
});

View File

@ -1,24 +0,0 @@
import { ObjectId } from 'mongodb'
export default defineEventHandler(async (event) => {
const { teamId } = await readBody(event);
const user = await getAuth(event)
const config = useRuntimeConfig()
const teamsColl = db.collection('teams')
const usersColl = db.collection('users')
const team = await teamsColl.findOneAndUpdate({ _id: new ObjectId(teamId) }, { $inc: { count: 1 } })
if (!team.value) return createError({ statusCode: 500, statusMessage: 'Team niet gevonden'})
await $fetch(config.discordHost + '/team/addteammember', {
method: 'POST',
body: { voiceChannelId: team.value.voiceChannelId, textChannelId: team.value.textChannelId, discordId: user.discord.id }
})
await usersColl.updateOne({ _id: new ObjectId(user._id) }, { $set: { 'team.id': teamId, 'team.admin': false }})
return team
});

View File

@ -1,8 +0,0 @@
export default defineEventHandler(async () => {
const teamsColl = db.collection('teams')
const cursor = teamsColl.find();
const teams = await cursor.toArray()
return teams
});

View File

@ -1,16 +0,0 @@
import { ObjectId } from 'mongodb'
export default defineEventHandler(async (event) => {
const { id } = await readBody(event);
const user = await getAuth(event)
const teamsColl = db.collection('teams')
const usersColl = db.collection('users')
const team = await teamsColl.findOne({ _id: new ObjectId(user.team.id) })
usersColl.updateOne({ _id: new ObjectId(id) }, { $pull: { teamInvites: team._id.toString() } })
return team
});

View File

@ -1,28 +0,0 @@
export default defineEventHandler(async (event) => {
const { teamName, teamColor } = await readBody(event);
if (!teamName || !teamColor) return createError({ statusCode: 400, statusMessage: 'Team naam en kleur zijn vereist' })
if (!isHexColor(teamColor)) return createError({ statusCode: 400, statusMessage: 'Team kleur is geen gelidige kleurencode' })
if (!verifyUsername(teamName)) return createError({ statusCode: 400, statusMessage: 'Team naam moet alfanumeriek zijn en mag maximaal 20 tekens lang zijn' })
const user = await getAuth(event)
const config = useRuntimeConfig()
if (user.team) return createError({ statusCode: 400, statusMessage: 'Gebruiker is al in een team' })
const teamsColl = db.collection('teams')
const usersColl = db.collection('users')
if (await teamsColl.findOne({ name: { $regex: new RegExp(teamName, "i") } })) return createError({ statusCode: 400, statusMessage: 'Team naam bestaat al' })
const discordResponse = await $fetch(config.discordHost + '/team/createchannels', {
method: 'POST',
body: { name: teamName, discordId: user.discord.id }
})
const response = await teamsColl.insertOne({ name: teamName, color: teamColor, count: 1, textChannelId: discordResponse.textChannel.id, voiceChannelId: discordResponse.voiceChannel.id })
await usersColl.findOneAndUpdate({ 'discord.id': user.discord.id }, { $set: { 'team.id': response.insertedId.toString(), 'team.admin': true } })
return response;
});

View File

@ -1,14 +0,0 @@
import { ObjectId } from 'mongodb'
export default defineEventHandler(async (event) => {
const { userId } = await readBody(event)
const user = await getAuth(event)
if (!user.team.admin) return createError({ statusCode: 403, statusMessage: 'Admin team rol vereist' })
const usersColl = db.collection('users')
await usersColl.findOneAndUpdate({ _id: new ObjectId(userId) }, { $set: { 'team.admin': false } });
return { status: 'Success' }
});

View File

@ -1,29 +0,0 @@
import { ObjectId } from 'mongodb'
export default defineEventHandler(async (event) => {
const { name, color } = await readBody(event);
if (!name || !color) return createError({ statusCode: 400, statusMessage: 'Team naam en kleur zijn vereist' })
if (!isHexColor(color)) return createError({ statusCode: 400, statusMessage: 'Team kleur is geen goede kleurencode' })
if (!verifyUsername(name)) return createError({ statusCode: 400, statusMessage: 'Team naam moet alfanumeriek zijn en mag maximaal 20 tekens lang zijn' })
const user = await getAuth(event)
const config = useRuntimeConfig()
if (!user.team.admin) return createError({ statusCode: 403, statusMessage: 'Admin team rol vereist' })
const teamsColl = db.collection('teams')
const team = await teamsColl.findOne({ _id: new ObjectId(user.team.id) });
if (team.name !== name && await teamsColl.findOne({ name: { $regex: new RegExp(name, "i") } })) return createError({ statusCode: 400, statusMessage: 'Team naam bestaat al' })
await $fetch(config.discordHost + '/team/edit', {
method: 'POST',
body: { voiceChannelId: team.voiceChannelId, textChannelId: team.textChannelId, name: name }
})
await teamsColl.updateOne({ _id: new ObjectId(user.team.id) }, { $set: { name: name, color: color } })
return team
});

Some files were not shown because too many files have changed in this diff Show More