Compare commits

...

3 Commits

Author SHA1 Message Date
d2ab24ed64 Added Team System 2023-05-09 13:57:01 +02:00
10183ac56b run eslint 2023-05-06 13:14:01 +02:00
8d358e694e build chat system 2023-05-06 13:09:46 +02:00
50 changed files with 1918 additions and 687 deletions

View File

@ -0,0 +1,17 @@
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,6 +1,6 @@
const { EmbedBuilder } = require('discord.js'); const { EmbedBuilder } = require('discord.js');
const registerEvents = ({ createEmbed, client }) => { const registerEvents = ({ client }) => {
client.player.on('trackStart', (queue, track) => { client.player.on('trackStart', (queue, track) => {

View File

@ -4,6 +4,7 @@ const { Player } = require('discord-player');
const fs = require('node:fs'); const fs = require('node:fs');
const path = require('node:path'); const path = require('node:path');
const dotenv = require('dotenv'); const dotenv = require('dotenv');
const express = require('express');
const createEmbed = require('./functions/createEmbed.js'); const createEmbed = require('./functions/createEmbed.js');
dotenv.config(); dotenv.config();
@ -15,11 +16,25 @@ const log = {
}; };
// Register client and music events // Register client and music events
const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildMembers] }); const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent] });
client.player = new Player(client); client.player = new Player(client);
require('./functions/player.js').registerEvents({ client, createEmbed }); require('./functions/player.js').registerEvents({ client, createEmbed });
// Express
const app = express();
app.use(express.json());
const messageRoute = require('./routes/Message');
app.use('/minecraft', messageRoute);
app.listen('4000', () => {
log.Info('Express app running');
});
// Command handling // Command handling
client.commands = new Collection(); client.commands = new Collection();
@ -53,3 +68,5 @@ for (const file of eventFiles) {
} }
client.login(process.env.DISCORD_TOKEN); client.login(process.env.DISCORD_TOKEN);
module.exports.client = client;

File diff suppressed because it is too large Load Diff

View File

@ -17,6 +17,8 @@
"discord.js": "^14.7.1", "discord.js": "^14.7.1",
"distube": "^4.0.4", "distube": "^4.0.4",
"dotenv": "^16.0.3", "dotenv": "^16.0.3",
"eslint": "^8.29.0" "eslint": "^8.29.0",
"express": "^4.18.2",
"ytdl-core": "^4.11.4"
} }
} }

View File

@ -0,0 +1,38 @@
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

@ -24,7 +24,6 @@ loom {
mods { mods {
"polarcraft-mod" { "polarcraft-mod" {
sourceSet sourceSets.main sourceSet sourceSets.main
sourceSet sourceSets.client
} }
} }

View File

@ -1,10 +0,0 @@
package com.xeovalyte.polarcraft;
import net.fabricmc.api.ClientModInitializer;
public class ExampleModClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
// This entrypoint is suitable for setting up client-specific logic, such as rendering.
}
}

View File

@ -1,15 +0,0 @@
package com.xeovalyte.polarcraft.mixin.client;
import net.minecraft.client.MinecraftClient;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(MinecraftClient.class)
public class ExampleClientMixin {
@Inject(at = @At("HEAD"), method = "run")
private void run(CallbackInfo info) {
// This code is injected into the start of MinecraftClient.run()V
}
}

View File

@ -1,11 +0,0 @@
{
"required": true,
"package": "com.xeovalyte.polarcraft.mixin.client",
"compatibilityLevel": "JAVA_17",
"client": [
"ExampleClientMixin"
],
"injectors": {
"defaultRequire": 1
}
}

View File

@ -1,33 +1,23 @@
package com.xeovalyte.polarcraft; package com.xeovalyte.polarcraft;
import net.fabricmc.api.ModInitializer; import com.xeovalyte.polarcraft.util.messageFunctions;
import com.xeovalyte.polarcraft.util.verifyFunction;
import net.fabricmc.api.ModInitializer;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.network.message.SignedMessage;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.io.File; import java.io.File;
import com.google.gson.JsonObject;
import com.google.gson.JsonElement;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import java.util.UUID;
import net.fabricmc.fabric.api.message.v1.ServerMessageEvents; import net.fabricmc.fabric.api.message.v1.ServerMessageEvents;
public class PolarcraftMod implements ModInitializer { public class PolarcraftMod implements ModInitializer {
@ -39,8 +29,9 @@ public class PolarcraftMod implements ModInitializer {
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
private static final File CONFIG_FILE = new File(FabricLoader.getInstance().getConfigDir().toFile(), "polarcraft.json"); private static final File CONFIG_FILE = new File(FabricLoader.getInstance().getConfigDir().toFile(), "polarcraft.json");
private String configWebhookUrl = "https://discordapp.com/api/webhooks/webhookid/webhooktoken"; public static String configChatMessageUrl = "https://example.com/api/minecraft/message/chattodiscord";
private String configVerifyUrl = "https://example.com/api/minecraft/verifyuuid"; public static String configGameMessageUrl = "https://example.com/api/minecraft/message/gametodiscord";
public static String configVerifyUrl = "https://example.com/api/minecraft/verifyuuid";
@Override @Override
public void onInitialize() { public void onInitialize() {
@ -51,15 +42,17 @@ public class PolarcraftMod implements ModInitializer {
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> { ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> {
ServerPlayerEntity player = (ServerPlayerEntity) handler.player; ServerPlayerEntity player = (ServerPlayerEntity) handler.player;
onPlayerJoin(player);
verifyFunction.onPlayerJoin(player);
});
ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> {
ServerPlayerEntity player = (ServerPlayerEntity) handler.player;
messageFunctions.sendGameMessage(player.getUuid(), player.getDisplayName().getString() + " left the game");
}); });
ServerMessageEvents.CHAT_MESSAGE.register((message, sender, params) -> { ServerMessageEvents.CHAT_MESSAGE.register((message, sender, params) -> {
onChatMessage(message, sender); messageFunctions.sendChatMessage(message, sender);
});
ServerMessageEvents.GAME_MESSAGE.register((server, message, overlay) -> {
onGameMessage(server, message);
}); });
} }
@ -71,7 +64,8 @@ public class PolarcraftMod implements ModInitializer {
try { try {
MyModConfig config = GSON.fromJson(FileUtils.readFileToString(CONFIG_FILE, StandardCharsets.UTF_8), MyModConfig.class); MyModConfig config = GSON.fromJson(FileUtils.readFileToString(CONFIG_FILE, StandardCharsets.UTF_8), MyModConfig.class);
configWebhookUrl = config.webhookUrl; configChatMessageUrl = config.chatMessageUrl;
configGameMessageUrl = config.gameMessageUrl;
configVerifyUrl = config.verifyUrl; configVerifyUrl = config.verifyUrl;
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
@ -80,120 +74,21 @@ public class PolarcraftMod implements ModInitializer {
private void saveConfig() { private void saveConfig() {
try { try {
FileUtils.writeStringToFile(CONFIG_FILE, GSON.toJson(new MyModConfig(configWebhookUrl, configVerifyUrl)), StandardCharsets.UTF_8); FileUtils.writeStringToFile(CONFIG_FILE, GSON.toJson(new MyModConfig(configChatMessageUrl, configGameMessageUrl,configVerifyUrl)), StandardCharsets.UTF_8);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
public static class MyModConfig { public static class MyModConfig {
public String webhookUrl; public String chatMessageUrl;
public String gameMessageUrl;
public String verifyUrl; public String verifyUrl;
public MyModConfig(String webhookUrl, String verifyUrl) { public MyModConfig(String chatMessageUrl, String gameMessageUrl,String verifyUrl) {
this.webhookUrl = webhookUrl; this.chatMessageUrl = chatMessageUrl;
this.gameMessageUrl = gameMessageUrl;
this.verifyUrl = verifyUrl; this.verifyUrl = verifyUrl;
} }
} }
private void onChatMessage( SignedMessage message, ServerPlayerEntity sender) {
try {
// Create a URL object for the server endpoint
URL url = new URL(configWebhookUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
String requestBody = "{\"content\":\"" + sender.getName().getString() + " > "+ message.getSignedContent() + "\"}";
OutputStream outputStream = connection.getOutputStream();
outputStream.write(requestBody.getBytes());
outputStream.flush();
outputStream.close();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private void onGameMessage( MinecraftServer server, Text message) {
try {
// Create a URL object for the server endpoint
URL url = new URL(configWebhookUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
String requestBody = "{\"content\":\"" + "**" + message.getString() + "**" + "\"}";
OutputStream outputStream = connection.getOutputStream();
outputStream.write(requestBody.getBytes());
outputStream.flush();
outputStream.close();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private void onPlayerJoin(ServerPlayerEntity player) {
UUID uuid = player.getUuid();
try {
URL url = new URL(configVerifyUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
String requestBody = "{\"uuid\":\"" + uuid + "\"}";
OutputStream outputStream = connection.getOutputStream();
outputStream.write(requestBody.getBytes());
outputStream.flush();
outputStream.close();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
Gson gson = new Gson();
JsonElement element = gson.fromJson(response.toString(), JsonElement.class);
JsonObject jsonResponse = element.getAsJsonObject();
boolean verified = jsonResponse.get("verified").getAsBoolean();
if (!verified) {
int code = jsonResponse.get("code").getAsInt();
player.networkHandler.disconnect(Text.literal("Whitelist yourself by using this code: " + code));
}
} catch (IOException e) {
player.networkHandler.disconnect(Text.literal("There was an error while verifing your account"));
e.printStackTrace();
}
}
} }

View File

@ -1,15 +0,0 @@
package com.xeovalyte.polarcraft.mixin;
import net.minecraft.server.MinecraftServer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(MinecraftServer.class)
public class ExampleMixin {
@Inject(at = @At("HEAD"), method = "loadWorld")
private void init(CallbackInfo info) {
// This code is injected into the start of MinecraftServer.loadWorld()V
}
}

View File

@ -0,0 +1,31 @@
package com.xeovalyte.polarcraft.mixin;
import com.xeovalyte.polarcraft.util.messageFunctions;
import net.minecraft.advancement.Advancement;
import net.minecraft.advancement.PlayerAdvancementTracker;
import net.minecraft.server.network.ServerPlayerEntity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Objects;
@Mixin(PlayerAdvancementTracker.class)
public class MixinPlayerAdvancementTracker {
@Shadow
private ServerPlayerEntity owner;
@Inject(method = "grantCriterion",
at = @At(
target = "Lnet/minecraft/server/PlayerManager;broadcast(Lnet/minecraft/text/Text;Z)V",
value = "INVOKE"
)
)
private void grantCriterion(Advancement advancement, String criterionName, CallbackInfoReturnable<Boolean> cir) {
// messageFunctions.sendGameMessage(owner.getUuid(), " has completed the challenge [" + Objects.requireNonNull(advancement.getDisplay()).getTitle().getString() + "]");
messageFunctions.sendGameMessage(owner.getUuid(), owner.getDisplayName().getString() + " has completed the advancement [" + Objects.requireNonNull(advancement.getDisplay()).getTitle().getString() + "]");
}
}

View File

@ -0,0 +1,27 @@
package com.xeovalyte.polarcraft.mixin;
import com.xeovalyte.polarcraft.util.messageFunctions;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
@Mixin(ServerPlayerEntity.class)
public class MixinPlayerEntity {
@Inject(method = "onDeath",
at = @At(
target = "Lnet/minecraft/server/network/ServerPlayNetworkHandler;sendPacket(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/PacketCallbacks;)V",
value = "INVOKE",
ordinal = 0),
locals = LocalCapture.CAPTURE_FAILSOFT
)
private void onDeath(DamageSource damageSource, CallbackInfo ci, boolean bl, Text text){
ServerPlayerEntity serverPlayerEntity = (ServerPlayerEntity) (Object) this;
messageFunctions.sendGameMessage(serverPlayerEntity.getUuid(), text.getString());
}
}

View File

@ -0,0 +1,74 @@
package com.xeovalyte.polarcraft.util;
import com.xeovalyte.polarcraft.PolarcraftMod;
import net.minecraft.network.message.SignedMessage;
import net.minecraft.server.network.ServerPlayerEntity;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.UUID;
public class messageFunctions {
public static void sendGameMessage(UUID uuid, String message) {
try {
// Create a URL object for the server endpoint
URL url = new URL(PolarcraftMod.configGameMessageUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
String requestBody = "{\"content\":\"" + message + "\", \"uuid\":\"" + uuid + "\"}";
OutputStream outputStream = connection.getOutputStream();
outputStream.write(requestBody.getBytes());
outputStream.flush();
outputStream.close();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void sendChatMessage( SignedMessage message, ServerPlayerEntity sender) {
try {
// Create a URL object for the server endpoint
URL url = new URL(PolarcraftMod.configChatMessageUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
String requestBody = "{\"content\":\"" + message.getSignedContent() + "\", \"uuid\":\"" + sender.getUuid() + "\"}";
OutputStream outputStream = connection.getOutputStream();
outputStream.write(requestBody.getBytes());
outputStream.flush();
outputStream.close();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,66 @@
package com.xeovalyte.polarcraft.util;
import com.xeovalyte.polarcraft.PolarcraftMod;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.UUID;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
public class verifyFunction {
public static void onPlayerJoin(ServerPlayerEntity player) {
UUID uuid = player.getUuid();
try {
URL url = new URL(PolarcraftMod.configVerifyUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
String requestBody = "{\"uuid\":\"" + uuid + "\"}";
OutputStream outputStream = connection.getOutputStream();
outputStream.write(requestBody.getBytes());
outputStream.flush();
outputStream.close();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
Gson gson = new Gson();
JsonElement element = gson.fromJson(response.toString(), JsonElement.class);
JsonObject jsonResponse = element.getAsJsonObject();
boolean verified = jsonResponse.get("verified").getAsBoolean();
if (!verified) {
int code = jsonResponse.get("code").getAsInt();
player.networkHandler.disconnect(Text.literal("Whitelist yourself by using this code: " + code));
} else {
messageFunctions.sendGameMessage(player.getUuid(), player.getDisplayName().getString() + " joined the game");
}
} catch (IOException e) {
player.networkHandler.disconnect(Text.literal("There was an error while verifing your account"));
e.printStackTrace();
}
}
}

View File

@ -13,18 +13,14 @@
}, },
"license": "CC0-1.0", "license": "CC0-1.0",
"icon": "assets/polarcraft-mod/icon.png", "icon": "assets/polarcraft-mod/icon.png",
"environment": "*", "environment": "server",
"entrypoints": { "entrypoints": {
"main": [ "main": [
"com.xeovalyte.polarcraft.PolarcraftMod" "com.xeovalyte.polarcraft.PolarcraftMod"
] ]
}, },
"mixins": [ "mixins": [
"polarcraft-mod.mixins.json", "polarcraft-mod.mixins.json"
{
"config": "polarcraft-mod.client.mixins.json",
"environment": "client"
}
], ],
"depends": { "depends": {
"fabricloader": ">=0.14.19", "fabricloader": ">=0.14.19",

View File

@ -3,7 +3,8 @@
"package": "com.xeovalyte.polarcraft.mixin", "package": "com.xeovalyte.polarcraft.mixin",
"compatibilityLevel": "JAVA_17", "compatibilityLevel": "JAVA_17",
"mixins": [ "mixins": [
"ExampleMixin" "MixinPlayerAdvancementTracker",
"MixinPlayerEntity"
], ],
"injectors": { "injectors": {
"defaultRequire": 1 "defaultRequire": 1

View File

@ -1,6 +1,6 @@
{ {
"root": true, "root": true,
"extends": ["@nuxt/eslint-config"], "extends": ["@nuxt/eslint-config", "plugin:tailwindcss/recommended"],
"rules": { "rules": {
"vue/max-attributes-per-line": ["error", { "vue/max-attributes-per-line": ["error", {
"singleline": { "singleline": {

View File

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

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="w-full text-primary flex items-center flex-col"> <div class="flex w-full flex-col items-center text-primary">
<div v-if="!user.minecraft.uuid" class="flex flex-col items-center"> <div v-if="!user.minecraft.uuid" class="flex flex-col items-center">
<p class="max-w-xl mb-10 sm:text-base text-sm"> <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 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 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. te zien, vul deze code hieronder in.

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="bg-neutral-800 border-b border-b-gray-700 flex items-center px-5"> <div class="flex items-center border-b border-b-gray-700 bg-neutral-800 px-5">
<h1 class="text-primary text-2xl font-bold">Polarcraft</h1> <h1 class="text-2xl font-bold text-primary">Polarcraft</h1>
<Button outline class="ml-auto" @click="logout">Logout</Button> <Button outline class="ml-auto" @click="logout">Logout</Button>
</div> </div>
</template> </template>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="bg-neutral-800 text-gray-300 px-2 pt-5 pb-14 flex flex-col"> <div class="flex flex-col bg-neutral-800 px-2 pb-14 pt-5 text-gray-300">
<NuxtLink to="/" class="sidebar-item"> <NuxtLink to="/" class="sidebar-item">
<Icon size="1.5em" name="ph:house" class="mr-3" /> <Icon size="1.5em" name="ph:house" class="mr-3" />
Home Home

View File

@ -0,0 +1,180 @@
<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:value="editTeamModal.name" background-class="bg-neutral-800" class="w-full max-w-sm">Naam / Prefix</Input>
<Colorpicker v-model:value="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 @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('Successfully left team')
} 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('Successfully modified team')
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(`Successfully invited ${usr.username}`)
} 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('Successfully cancelled invited')
} 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('Successfully promted user')
} 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('Successfully demoted user')
} catch (e) {
console.log(e);
useToast().error(e.statusMessage)
}
}
</script>

View File

@ -1,20 +1,86 @@
<template> <template>
<p class="text-gray-300 text-center my-10"> <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 Je zit momenteel niet in een team. Maak een team aan of wacht tot dat je geinvite wordt
</p> </p>
<div class="flex w-full justify-center gap-10 mb-5"> <div class="mb-5 flex w-full justify-center gap-10">
<span class="text-primary font-bold hover:cursor-pointer" :class="{'underline underline-offset-4': !createTeam}" @click="createTeam = false">Team Invites</span> <span
<span class="text-primary font-bold hover:cursor-pointer" :class="{'underline underline-offset-4': createTeam}" @click="createTeam = true">Create Team</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>
<div v-if="!createTeam" class="text-center text-gray-300"> <div v-if="!createTeam.show" class="text-center text-gray-300">
You don't have any team invites <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>
<div v-else class="w-full flex flex-col items-center gap-5"> <div v-else class="flex w-full flex-col items-center gap-5">
<Input class="w-full max-w-sm">Naam / Prefix</Input> <Input v-model:value="createTeam.name" class="w-full max-w-sm">Naam / Prefix</Input>
<Colorpicker class="w-full max-w-sm" /> <Colorpicker v-model:value="createTeam.color" class="w-full max-w-sm" />
<Button @click="handleCreateTeam">Create Team</Button>
</div> </div>
</template> </template>
<script setup> <script setup>
const createTeam = ref(false) 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 }
})
console.log(response)
user.value.team = { id: response._id, admin: false }
useToast().success('Successfully joined 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('Successfully created team')
} catch (e) {
console.log(e);
useToast().error(e.statusMessage)
}
}
</script> </script>

View File

@ -1,14 +1,14 @@
<template> <template>
<div class="h-full bg-neutral-900"> <div class="h-full bg-neutral-900">
<div class="hidden sm:grid grid-cols-desktoplayout grid-rows-desktoplayout h-full"> <div v-if="user" class="hidden h-full grid-cols-desktoplayout grid-rows-desktoplayout sm:grid">
<LayoutNavbar class="col-span-2" /> <LayoutNavbar class="col-span-2" />
<LayoutSidebar v-if="user.minecraft.uuid" class="" /> <LayoutSidebar v-if="user.minecraft.uuid" class="" />
<div class="overflow-y-auto px-10 pt-5" :class="{ 'col-span-2': !user.minecraft.uuid }"> <div class="overflow-y-auto px-10 pt-5" :class="{ 'col-span-2': !user.minecraft.uuid }">
<NuxtPage /> <NuxtPage />
</div> </div>
</div> </div>
<div class="sm:hidden h-full"> <div v-if="user" class="h-full sm:hidden">
<div class="overflow-y-auto px-2 py-2"> <div class="overflow-y-auto p-2">
<NuxtPage /> <NuxtPage />
</div> </div>
</div> </div>

View File

@ -13,11 +13,14 @@ export default defineNuxtConfig({
runtimeConfig: { runtimeConfig: {
discordId: '', discordId: '',
discordSecret: '', discordSecret: '',
discordHost: '',
jwtSecret: '', jwtSecret: '',
dbUrl: '', dbUrl: '',
mineckerHost: '', mineckerHost: '',
mineckerApiKey: '', mineckerApiKey: '',
mincecraftContainer: 'mc-school', rconPassword: '',
rconPort: '25575',
rconHost: 'localhost',
public: { 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', redirectUrl: 'https://discord.com/api/oauth2/authorize?client_id=1052974736432443432&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth&response_type=code&scope=identify',
} }

61
web/package-lock.json generated
View File

@ -11,6 +11,7 @@
"@xeovalyte/nuxt-xvtoast": "^1.1.3", "@xeovalyte/nuxt-xvtoast": "^1.1.3",
"@xeovalyte/nuxt-xvui": "git+https://gitea.xeovalyte.dev/xeovalyte/nuxt-xvui.git", "@xeovalyte/nuxt-xvui": "git+https://gitea.xeovalyte.dev/xeovalyte/nuxt-xvui.git",
"jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.0",
"minecraft-server-util": "^5.4.2",
"mongodb": "^5.3.0", "mongodb": "^5.3.0",
"socket.io-client": "^4.6.1", "socket.io-client": "^4.6.1",
"surrealdb.js": "^0.6.0" "surrealdb.js": "^0.6.0"
@ -24,6 +25,7 @@
"@vueuse/core": "^10.1.0", "@vueuse/core": "^10.1.0",
"@vueuse/nuxt": "^10.1.0", "@vueuse/nuxt": "^10.1.0",
"eslint": "^8.39.0", "eslint": "^8.39.0",
"eslint-plugin-tailwindcss": "^3.11.0",
"nuxt": "^3.4.2", "nuxt": "^3.4.2",
"nuxt-icon": "^0.3.3" "nuxt-icon": "^0.3.3"
} }
@ -3158,7 +3160,7 @@
} }
}, },
"node_modules/@xeovalyte/nuxt-xvui": { "node_modules/@xeovalyte/nuxt-xvui": {
"resolved": "git+https://gitea.xeovalyte.dev/xeovalyte/nuxt-xvui.git#e5bb7302f0626b39aa7b3a73bd4109b03702a9c7", "resolved": "git+https://gitea.xeovalyte.dev/xeovalyte/nuxt-xvui.git#53b253beae85b014c2556291aaa9f8f94fc765d5",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@nuxt/eslint-config": "^0.1.1", "@nuxt/eslint-config": "^0.1.1",
@ -5357,6 +5359,22 @@
"url": "https://opencollective.com/eslint" "url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/eslint-plugin-tailwindcss": {
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-tailwindcss/-/eslint-plugin-tailwindcss-3.11.0.tgz",
"integrity": "sha512-RaraOG4D6VXutKnoNvFQ4+frTWGJDKtezy1yCrGFS7Um1to/npDNdh2GL19IRoGB/eanbtwhxFXy+xyEw0grAg==",
"dev": true,
"dependencies": {
"fast-glob": "^3.2.5",
"postcss": "^8.4.4"
},
"engines": {
"node": ">=12.13.0"
},
"peerDependencies": {
"tailwindcss": "^3.2.2"
}
},
"node_modules/eslint-plugin-vue": { "node_modules/eslint-plugin-vue": {
"version": "9.11.0", "version": "9.11.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.11.0.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.11.0.tgz",
@ -8092,6 +8110,22 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/minecraft-motd-util": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/minecraft-motd-util/-/minecraft-motd-util-1.1.12.tgz",
"integrity": "sha512-5TuTRjrRupSTruea0nRC37r0FdhkS1O4wIJKAYfwJRCQd/X4Zyl/dVIs96h9UVW6N8jhIuz9pNkrDsqyN7VBdA=="
},
"node_modules/minecraft-server-util": {
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/minecraft-server-util/-/minecraft-server-util-5.4.2.tgz",
"integrity": "sha512-QjCM23pnXya+Fi2i3VxpQ5RS6luBA6Tkr0GAIG9dVhOrG5fBw10MVQyd13fJ4RquY3jQwNiHGX05G/0lnrQD5A==",
"dependencies": {
"minecraft-motd-util": "^1.1.9"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@ -15804,7 +15838,7 @@
} }
}, },
"@xeovalyte/nuxt-xvui": { "@xeovalyte/nuxt-xvui": {
"version": "git+https://gitea.xeovalyte.dev/xeovalyte/nuxt-xvui.git#e5bb7302f0626b39aa7b3a73bd4109b03702a9c7", "version": "git+https://gitea.xeovalyte.dev/xeovalyte/nuxt-xvui.git#53b253beae85b014c2556291aaa9f8f94fc765d5",
"from": "@xeovalyte/nuxt-xvui@git+https://gitea.xeovalyte.dev/xeovalyte/nuxt-xvui.git", "from": "@xeovalyte/nuxt-xvui@git+https://gitea.xeovalyte.dev/xeovalyte/nuxt-xvui.git",
"requires": { "requires": {
"@nuxt/eslint-config": "^0.1.1", "@nuxt/eslint-config": "^0.1.1",
@ -17427,6 +17461,16 @@
} }
} }
}, },
"eslint-plugin-tailwindcss": {
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-tailwindcss/-/eslint-plugin-tailwindcss-3.11.0.tgz",
"integrity": "sha512-RaraOG4D6VXutKnoNvFQ4+frTWGJDKtezy1yCrGFS7Um1to/npDNdh2GL19IRoGB/eanbtwhxFXy+xyEw0grAg==",
"dev": true,
"requires": {
"fast-glob": "^3.2.5",
"postcss": "^8.4.4"
}
},
"eslint-plugin-vue": { "eslint-plugin-vue": {
"version": "9.11.0", "version": "9.11.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.11.0.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.11.0.tgz",
@ -19460,6 +19504,19 @@
"integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
"dev": true "dev": true
}, },
"minecraft-motd-util": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/minecraft-motd-util/-/minecraft-motd-util-1.1.12.tgz",
"integrity": "sha512-5TuTRjrRupSTruea0nRC37r0FdhkS1O4wIJKAYfwJRCQd/X4Zyl/dVIs96h9UVW6N8jhIuz9pNkrDsqyN7VBdA=="
},
"minecraft-server-util": {
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/minecraft-server-util/-/minecraft-server-util-5.4.2.tgz",
"integrity": "sha512-QjCM23pnXya+Fi2i3VxpQ5RS6luBA6Tkr0GAIG9dVhOrG5fBw10MVQyd13fJ4RquY3jQwNiHGX05G/0lnrQD5A==",
"requires": {
"minecraft-motd-util": "^1.1.9"
}
},
"minimatch": { "minimatch": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",

View File

@ -7,7 +7,8 @@
"generate": "nuxt generate", "generate": "nuxt generate",
"preview": "nuxt preview", "preview": "nuxt preview",
"postinstall": "nuxt prepare", "postinstall": "nuxt prepare",
"lint": "eslint ." "lint": "eslint .",
"lint-fix": "eslint . --fix"
}, },
"devDependencies": { "devDependencies": {
"@nuxt/devtools": "^0.4.2", "@nuxt/devtools": "^0.4.2",
@ -18,6 +19,7 @@
"@vueuse/core": "^10.1.0", "@vueuse/core": "^10.1.0",
"@vueuse/nuxt": "^10.1.0", "@vueuse/nuxt": "^10.1.0",
"eslint": "^8.39.0", "eslint": "^8.39.0",
"eslint-plugin-tailwindcss": "^3.11.0",
"nuxt": "^3.4.2", "nuxt": "^3.4.2",
"nuxt-icon": "^0.3.3" "nuxt-icon": "^0.3.3"
}, },
@ -26,6 +28,7 @@
"@xeovalyte/nuxt-xvtoast": "^1.1.3", "@xeovalyte/nuxt-xvtoast": "^1.1.3",
"@xeovalyte/nuxt-xvui": "git+https://gitea.xeovalyte.dev/xeovalyte/nuxt-xvui.git", "@xeovalyte/nuxt-xvui": "git+https://gitea.xeovalyte.dev/xeovalyte/nuxt-xvui.git",
"jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.0",
"minecraft-server-util": "^5.4.2",
"mongodb": "^5.3.0", "mongodb": "^5.3.0",
"socket.io-client": "^4.6.1", "socket.io-client": "^4.6.1",
"surrealdb.js": "^0.6.0" "surrealdb.js": "^0.6.0"

View File

@ -1,16 +1,16 @@
<template> <template>
<div class="w-full h-full text-primary flex flex-col"> <div class="flex h-full w-full flex-col text-primary">
<h1 class="text-5xl font-bold text-center mt-20 mb-10">Welkom, {{ user.discord.username }}</h1> <h1 class="mb-10 mt-20 text-center text-5xl font-bold">Welkom, {{ user.discord.username }}</h1>
<Whitelist v-if="!user.minecraft.uuid" /> <Whitelist v-if="!user.minecraft.uuid" />
<div v-else class="flex justify-center gap-4 flex-wrap max-w-3xl w-full"> <div v-else class="flex w-full max-w-3xl flex-wrap justify-center gap-4">
<img :src="'https://api.mineatar.io/face/' + user.minecraft.uuid + '?scale=16'" class="w-24 rounded shadow"> <img :src="'https://api.mineatar.io/face/' + user.minecraft.uuid + '?scale=16'" class="w-24 rounded shadow">
<div class="rounded flex border-2 border-primary p-4 w-full max-w-md"> <div class="flex w-full max-w-md rounded border-2 border-primary p-4">
<ul class="my-auto"> <ul class="my-auto">
<li>Username: <b>{{ user.minecraft.username }}</b></li> <li>Username: <b>{{ user.minecraft.username }}</b></li>
<li>UUID: <b>{{ user.minecraft.uuid }}</b></li> <li>UUID: <b>{{ user.minecraft.uuid }}</b></li>
</ul> </ul>
</div> </div>
<div class="w-full flex justify-center gap-4 mt-2"> <div class="mt-2 flex w-full justify-center gap-4">
<Button type="danger" @click="removeWhitelist"> <Button type="danger" @click="removeWhitelist">
Remove from whitelist Remove from whitelist
</Button> </Button>

View File

@ -1,16 +1,16 @@
<template> <template>
<div class="w-full h-screen text-primary bg-opacity-0 flex justify-center items-center flex-col px-2"> <div class="flex h-screen w-full flex-col items-center justify-center bg-transparent px-2 text-primary">
<h1 class="sm:text-5xl text-3xl font-bold text-center mb-5">Polarcraft S5</h1> <h1 class="mb-5 text-center text-3xl font-bold sm:text-5xl">Polarcraft S5</h1>
<p class="max-w-xl mb-10 sm:text-base text-sm"> <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="bg-black bg-opacity-50 px-2 rounded">/ticket</span> commando uit te voeren. <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> </p>
<Button @click="navigateTo(config.public.redirectUrl, { external: true })"> <Button @click="navigateTo(config.public.redirectUrl, { external: true })">
Log in with Discord Log in with Discord
<Icon size="1.6em" name="ic:baseline-discord" /> <Icon size="1.6em" name="ic:baseline-discord" />
</Button> </Button>
<div class="absolute top-0 left-0 -z-10 w-full h-screen overflow-hidden"> <div class="absolute left-0 top-0 -z-10 h-screen w-full overflow-hidden">
<img src="../assets/pictures/diamond_wall.png" class="h-full w-full object-cover brightness-75 blur-[1px] scale-105"> <img src="../assets/pictures/diamond_wall.png" class="h-full w-full scale-105 object-cover blur-[1px] brightness-75">
</div> </div>
</div> </div>
</template> </template>

View File

@ -1,8 +1,17 @@
<template> <template>
<div> <div>
<h1 class="font-bold text-2xl text-primary"> <h1 class="text-2xl font-bold text-primary">
Team Team
</h1> </h1>
<TeamNone /> <TeamNone v-if="!user.team" />
<TeamDefault v-else />
</div> </div>
</template> </template>
<script setup>
const user = useState('user')
definePageMeta({
middleware: ["auth"]
})
</script>

View File

@ -37,7 +37,7 @@ export default defineEventHandler(async (event) => {
}, },
} }
await coll.updateOne({ 'discord.id': userResult.id }, { $set: doc, $setOnInsert: { minecraft: { uuid: null, username: null }, team: null } }, { upsert: true }) await coll.updateOne({ 'discord.id': userResult.id }, { $set: doc, $setOnInsert: { minecraft: { uuid: null, username: null }, teamInvites: [] } }, { upsert: true })
const token = createToken(tokenResponseData.access_token, tokenResponseData.refresh_token, tokenResponseData.expires_in, userResult.id ) const token = createToken(tokenResponseData.access_token, tokenResponseData.refresh_token, tokenResponseData.expires_in, userResult.id )

View File

@ -0,0 +1,20 @@
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/sendchatmessage', {
method: 'POST',
body: {
username: doc.discord.username + ' | ' + doc.minecraft.username,
// avatarURL: 'https://cdn.discordapp.com/avatars/' + doc.discord.id + '/' + doc.discord.avatarHash + '.png',
avatarURL: 'https://api.mineatar.io/face/' + doc.minecraft.uuid + '?scale=16',
content: content,
}
})
return { code: 'success' }
});

View File

@ -0,0 +1,10 @@
export default defineEventHandler(async (event) => {
const { discordId, content } = await readBody(event);
const coll = db.collection('users');
const doc = await coll.findOne({ 'discord.id': discordId });
await sendRconCommand(`tellraw @a {"text":"(DC) ${doc.discord.username} > ${content}"}`)
return { whoo: 'hi' }
});

View File

@ -0,0 +1,18 @@
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 { code: 'success' }
});

View File

@ -0,0 +1,18 @@
import { ObjectId } from 'mongodb'
export default defineEventHandler(async (event) => {
const { teamId } = await readBody(event);
const user = await getAuth(event)
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: 'Could not find team'})
await usersColl.updateOne({ _id: new ObjectId(user._id) }, { $set: { 'team.id': teamId, 'team.admin': false }})
return team
});

View File

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

View File

@ -0,0 +1,16 @@
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

@ -0,0 +1,21 @@
export default defineEventHandler(async (event) => {
const { teamName, teamColor } = await readBody(event);
if (!teamName || !teamColor) return createError({ statusCode: 400, statusMessage: 'teamName and teamColor are required' })
if (!isHexColor(teamColor)) return createError({ statusCode: 400, statusMessage: 'Team color is not a valid hex code' })
const user = await getAuth(event)
if (user.team) return createError({ statusCode: 400, statusMessage: 'User already is in a team' })
const teamsColl = db.collection('teams')
const usersColl = db.collection('users')
if (await teamsColl.findOne({ name: teamName })) return createError({ statusCode: 400, statusMessage: 'Team name already exists' })
const response = await teamsColl.insertOne({ name: teamName, color: teamColor, count: 1 })
await usersColl.findOneAndUpdate({ 'discord.id': user.discord.id }, { $set: { 'team.id': response.insertedId.toString(), 'team.admin': true } })
return response;
});

View File

@ -0,0 +1,14 @@
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: 'Forbidden' })
const usersColl = db.collection('users')
await usersColl.findOneAndUpdate({ _id: new ObjectId(userId) }, { $set: { 'team.admin': false } });
return { status: 'Success' }
});

View File

@ -0,0 +1,19 @@
import { ObjectId } from 'mongodb'
export default defineEventHandler(async (event) => {
const { name, color } = await readBody(event);
if (!isHexColor(color)) return createError({ statusCode: 400, statusMessage: 'Team color is not a valid hex code' })
const user = await getAuth(event)
const teamsColl = db.collection('teams')
const team = await teamsColl.findOne({ _id: new ObjectId(user.team.id) });
if (team.name !== name && await teamsColl.findOne({ name: name })) return createError({ statusCode: 400, statusMessage: 'Team name already exists' })
await teamsColl.updateOne({ _id: new ObjectId(user.team.id) }, { $set: { name: name, color: color } })
return team
});

View File

@ -0,0 +1,10 @@
import { ObjectId } from 'mongodb'
export default defineEventHandler(async (event) => {
const user = await getAuth(event)
const teamsColl = db.collection('teams')
const team = await teamsColl.findOne({ _id: new ObjectId(user.team.id) })
return team
});

View File

@ -0,0 +1,20 @@
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) })
const invitedUser = await usersColl.findOne({ _id: new ObjectId(id)});
if (invitedUser.team) return createError({ statusCode: 400, statusMessage: 'User already is in a team' })
usersColl.updateOne({ _id: new ObjectId(id) }, { $push: { teamInvites: team._id.toString() } })
return team
});

View File

@ -0,0 +1,16 @@
import { ObjectId } from 'mongodb'
export default defineEventHandler(async (event) => {
const user = await getAuth(event)
const teamsColl = db.collection('teams')
const team = await teamsColl.findOneAndUpdate({ _id: new ObjectId(user.team.id) }, { $inc: { count: -1 }})
const usersColl = db.collection('users')
await usersColl.findOneAndUpdate({ _id: new ObjectId(user._id) }, { $unset: { team: "" } })
if (team.value.count <= 1) {
await teamsColl.deleteOne({ _id: new ObjectId(user.team.id )})
}
return team
});

View File

@ -0,0 +1,17 @@
export default defineEventHandler(async (event) => {
const user = await getAuth(event)
const usersColl = db.collection('users')
const cursor = usersColl.find({ 'team.id': user.team.id })
if((await usersColl.countDocuments({ 'team.id': user.team.id })) === 0) {
return createError({ statusCode: 500, statusMessage: 'No users were found' })
}
const users = [];
for await (const doc of cursor) {
users.push(doc)
}
return users;
});

View File

@ -0,0 +1,14 @@
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: 'Forbidden' })
const usersColl = db.collection('users')
await usersColl.findOneAndUpdate({ _id: new ObjectId(userId) },{ $set: { 'team.admin': true } });
return { status: 'Success' }
});

View File

@ -0,0 +1,7 @@
export default defineEventHandler(async (event) => {
const usersColl = db.collection('users')
const cursor = usersColl.find({ team: { $exists: false } })
const unaffiliatedUsers = await cursor.toArray()
return unaffiliatedUsers
});

View File

@ -0,0 +1,4 @@
export const isHexColor = (str) => {
const pattern = /^#([0-9A-F]{3}){1,2}$/i;
return pattern.test(str);
}

41
web/server/utils/rcon.js Normal file
View File

@ -0,0 +1,41 @@
import { RCON } from 'minecraft-server-util';
const client = new RCON()
const config = useRuntimeConfig()
let connected = false;
export const sendRconCommand = async (command) => {
await connectRcon()
const message = await client.execute(command);
closeClient()
return message;
}
const connectRcon = async () => {
if (connected) return;
await client.connect(config.rconHost);
await client.login(config.rconPassword);
connected = true;
}
const debounce = (callback, wait) => {
let timeout = null;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => {
callback.apply(null, args);
}, wait);
};
}
const closeClient = debounce(() => {
client.close();
connected = false;
}, 5000)