Initial commit

This commit is contained in:
Baipyrus 2024-01-13 16:05:16 +00:00
commit 7d1b869b86
46 changed files with 5550 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.DS_Store
node_modules
.env
.env.*
!.env.example

2
discord-bot/.env.example Normal file
View File

@ -0,0 +1,2 @@
TOKEN=
CLIENT=

11
discord-bot/.eslintignore Normal file
View File

@ -0,0 +1,11 @@
.DS_Store
node_modules
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

View File

@ -0,0 +1,12 @@
{
"env": {
"node": true,
"es2021": true
},
"extends": ["eslint:recommended", "prettier"],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"rules": {}
}

0
discord-bot/.gitignore vendored Normal file
View File

View File

@ -0,0 +1,11 @@
.DS_Store
node_modules
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

8
discord-bot/.prettierrc Normal file
View File

@ -0,0 +1,8 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": [],
"overrides": []
}

3
discord-bot/README.md Normal file
View File

@ -0,0 +1,3 @@
# L Pa So Discord-Bot
Discord invite link [here](https://discord.com/api/oauth2/authorize?client_id=1184493544698810450&permissions=8&scope=bot%20applications.commands).

View File

@ -0,0 +1,26 @@
import { SlashCommandBuilder } from 'discord.js';
export const data = new SlashCommandBuilder()
.setName('get')
.setDescription('Google GET Request.')
.addStringOption((option) =>
option
.setName('api')
.setRequired(true)
.setAutocomplete(true)
.setDescription('Specify API to call GET.')
);
export async function autocomplete(interaction) {
const APIs = ['serverlist', 'read-tournament', 'announcements'];
const focused = interaction.options.getString('api');
const filtered = APIs.filter((choice) => choice.startsWith(focused));
await interaction.respond(filtered.map((choice) => ({ name: choice, value: choice })));
}
export async function execute(interaction) {
const endpoint = interaction.options.getString('api');
const response = await fetch(`http://localhost:5173/api/${endpoint}`);
const text = await response.text();
await interaction.reply({ content: text, ephemeral: true });
}

View File

@ -0,0 +1,36 @@
import {
ActionRowBuilder,
ModalBuilder,
SlashCommandBuilder,
TextInputBuilder,
TextInputStyle
} from 'discord.js';
export const data = new SlashCommandBuilder()
.setName('login')
.setDescription('Opens a login pop-up.');
export async function modalSubmit(interaction) {
await interaction.reply({ content: 'Successfully submitted Form!', ephemeral: true });
}
export async function execute(interaction) {
const modal = new ModalBuilder().setCustomId('login-modal').setTitle('Login Form');
const user = new ActionRowBuilder().addComponents(
new TextInputBuilder()
.setCustomId('user')
.setLabel('Enter username:')
.setStyle(TextInputStyle.Short)
.setRequired(true)
);
const password = new ActionRowBuilder().addComponents(
new TextInputBuilder()
.setCustomId('password')
.setLabel('Enter password:')
.setStyle(TextInputStyle.Short)
.setRequired(true)
);
modal.addComponents(user, password);
await interaction.showModal(modal);
}

View File

@ -0,0 +1,6 @@
import { SlashCommandBuilder } from 'discord.js';
export const data = new SlashCommandBuilder().setName('ping').setDescription('Replies with Pong!');
export async function execute(interaction) {
await interaction.reply({ content: 'Pong!', ephemeral: true });
}

View File

@ -0,0 +1,42 @@
import {
ActionRowBuilder,
ComponentType,
RoleSelectMenuBuilder,
SlashCommandBuilder
} from 'discord.js';
export const data = new SlashCommandBuilder()
.setName('role')
.setDMPermission(false)
.setDescription('Provides a role selector.');
export async function execute(interaction) {
const roles = await interaction.guild.roles.fetch();
const choices = roles.filter((r) => r.name.startsWith('test')).map((r) => r.id);
const button = new RoleSelectMenuBuilder()
.setMinValues(1)
.setMaxValues(25)
.setCustomId('role')
.setDefaultRoles(choices)
.setPlaceholder('Select at least one role.');
const row = new ActionRowBuilder().addComponents(button);
const response = await interaction.reply({
components: [row],
ephemeral: true
});
const collector = response.createMessageComponentCollector({
componentType: ComponentType.RoleSelect,
time: 120_000
});
collector.on('collect', async (i) => {
const selection = roles
.filter((r) => i.values.includes(r.id))
.map((r) => r.name)
.join(', ');
await i.reply({ content: `You have selected: "${selection}".`, ephemeral: true });
});
}

71
discord-bot/deploy.js Normal file
View File

@ -0,0 +1,71 @@
import { REST, Routes } from 'discord.js';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { promisify } from 'util';
import { config } from 'dotenv';
import { readdir } from 'fs';
config();
const readdirAsync = promisify(readdir);
const required = ['data', 'execute'];
const optional = ['autocomplete', 'modalSubmit'];
// Construct and prepare an instance of the REST module
const rest = new REST().setToken(process.env.TOKEN);
// and deploy your commands!
const putCommands = async (commands) => {
try {
console.info(`[INFO] Started refreshing ${commands.length} application (/) commands.`);
// The put method is used to fully refresh all commands in the guild with the current set
const data = await rest.put(Routes.applicationCommands(process.env.CLIENT), { body: commands });
console.info(`[INFO] Successfully reloaded ${data.length} application (/) commands.`);
} catch (error) {
// And of course, make sure you catch and log any errors!
console.error('[ERROR] Uncaught error:');
console.error(error);
}
};
// Register commands from sub-directories
const __dirname = dirname(fileURLToPath(import.meta.url));
const mainPath = join(__dirname, 'commands');
readdirAsync(mainPath)
.then(
async (categories) =>
// For each category directory
await Promise.all(
categories.map(async (category) => {
const catPath = join(mainPath, category);
const files = (await readdirAsync(catPath)).filter((file) => file.endsWith('.js'));
// For each command file
return await Promise.all(
files.map(async (current) => {
const filePath = join(catPath, current);
const command = await import(filePath);
// Warn incomplete commands
if (!required.every((name) => name in command)) {
console.error(
`[ERROR] The command at ${filePath} is missing a required "data" or "execute" property.`
);
return 0;
}
const properties = optional.filter((name) => !(name in command));
if (properties.length > 0)
properties.forEach((name) =>
console.warn(
`[WARNING] The command at ${filePath} is missing a optional "${name}" property.`
)
);
// Add command to collection
return command.data.toJSON();
})
);
})
)
)
.then((commands) => commands.reduce((a, i) => a.concat(i), []).filter((e) => e !== 0))
.then(putCommands);

View File

@ -0,0 +1,61 @@
import { Events } from 'discord.js';
const chatInputCommand = async (interaction, command) => {
// Try executing command
try {
console.info(`[INFO] Command ${interaction.commandName} was executed.`);
await command.execute(interaction);
} catch (error) {
console.error(error);
// Follow up/reply with error message
if (interaction.replied || interaction.deferred)
await interaction.followUp({
content: 'There was an error while executing this command!',
ephemeral: true
});
else
await interaction.reply({
content: 'There was an error while executing this command!',
ephemeral: true
});
}
};
const genericExecute = async (interaction, command, name, description) => {
try {
console.info(`[INFO] Command ${interaction.commandName} ${description ?? `used "${name}"`}.`);
await command[name](interaction);
} catch (error) {
console.error(error);
}
};
export const name = Events.InteractionCreate;
export async function execute(interaction) {
let command = interaction.client.commands.get(interaction.commandName);
// Execute slash commands
if (interaction.isChatInputCommand()) {
await chatInputCommand(interaction, command);
return;
}
// Autocomplete input
if (interaction.isAutocomplete() && 'autocomplete' in command) {
await genericExecute(interaction, command, 'autocomplete');
return;
}
// Modeal submit event
if (interaction.isModalSubmit()) {
const name = interaction.customId.split('-')[0];
command = interaction.client.commands.get(name);
await genericExecute(interaction, command, 'modalSubmit', 'submitted a modal');
return;
}
// Check if command exists
if (interaction.commandName && !command) {
console.warn(`[WARNING] No command matching ${interaction.commandName} was found.`);
return;
}
}

View File

@ -0,0 +1,7 @@
import { Events } from 'discord.js';
export const name = Events.ClientReady;
export const once = true;
export function execute(client) {
console.info(`[INFO] Ready! Logged in as ${client.user.tag}`);
}

81
discord-bot/index.js Normal file
View File

@ -0,0 +1,81 @@
import { Client, Collection, GatewayIntentBits } from 'discord.js';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { promisify } from 'util';
import { config } from 'dotenv';
import { readdir } from 'fs';
config();
const readdirAsync = promisify(readdir);
const required = ['data', 'execute'];
const optional = ['autocomplete', 'modalSubmit'];
const runClient = (commands, events) => {
// Create a new client instance
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.commands = new Collection();
commands.forEach((c) => client.commands.set(c.data.name, c));
events.forEach((e) =>
e.once
? client.once(e.name, (...args) => e.execute(...args))
: client.on(e.name, (...args) => e.execute(...args))
);
// Log in to Discord with your client's token
client.login(process.env.TOKEN);
};
// Register commands from sub-directories
const __dirname = dirname(fileURLToPath(import.meta.url));
const cmdPath = join(__dirname, 'commands');
const evtPath = join(__dirname, 'events');
readdirAsync(cmdPath)
.then(
async (categories) =>
// For each category directory
await Promise.all(
categories.map(async (category) => {
const catPath = join(cmdPath, category);
const content = await readdirAsync(catPath);
const files = content.filter((file) => file.endsWith('.js'));
// For each command file
return await Promise.all(
files.map(async (current) => {
const filePath = join(catPath, current);
const command = await import(filePath);
// Warn incomplete commands
if (!required.every((name) => name in command)) {
console.error(
`[ERROR] The command at ${filePath} is missing a required "data" or "execute" property.`
);
return 0;
}
const properties = optional.filter((name) => !(name in command));
if (properties.length > 0)
properties.forEach((name) =>
console.warn(
`[WARNING] The command at ${filePath} is missing a optional "${name}" property.`
)
);
// Add command to collection
return command;
})
);
})
)
)
.then((commands) => commands.reduce((a, i) => a.concat(i), []).filter((e) => e !== 0))
.then(async (commands) => {
const content = await readdirAsync(evtPath);
const files = content.filter((file) => file.endsWith('.js'));
const events = await Promise.all(
files.map(async (current) => {
const filePath = join(evtPath, current);
const command = await import(filePath);
// Add event to collection
return command;
})
);
runClient(commands, events);
});

1711
discord-bot/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

26
discord-bot/package.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "discord-bot",
"version": "0.0.1",
"description": "",
"private": true,
"main": "index.js",
"scripts": {
"start": "node .",
"deploy": "node deploy.js",
"lint": "prettier --check . && eslint .",
"format": "prettier --write ."
},
"author": "",
"license": "ISC",
"dependencies": {
"discord.js": "^14.14.1",
"dotenv": "^16.3.1",
"proxy-agent": "^6.3.1"
},
"devDependencies": {
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"prettier": "^3.1.1"
},
"type": "module"
}

3
website/.env.example Normal file
View File

@ -0,0 +1,3 @@
# Private
TEMP_FOLDER_LOCATION=
CONFIG_FOLDER_LOCATION=

13
website/.eslintignore Normal file
View File

@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

15
website/.eslintrc.cjs Normal file
View File

@ -0,0 +1,15 @@
/** @type { import("eslint").Linter.FlatConfig } */
module.exports = {
root: true,
extends: ['eslint:recommended', 'plugin:svelte/recommended', 'prettier'],
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020,
extraFileExtensions: ['.svelte']
},
env: {
browser: true,
es2017: true,
node: true
}
};

9
website/.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
/build
/.svelte-kit
/package
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
temp
config
backend_files

1
website/.npmrc Normal file
View File

@ -0,0 +1 @@
engine-strict=true

13
website/.prettierignore Normal file
View File

@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

8
website/.prettierrc Normal file
View File

@ -0,0 +1,8 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

1
website/README.md Normal file
View File

@ -0,0 +1 @@
# L Pa So

3019
website/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

27
website/package.json Normal file
View File

@ -0,0 +1,27 @@
{
"name": "website",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"lint": "prettier --check . && eslint .",
"format": "prettier --write ."
},
"devDependencies": {
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/kit": "^1.27.4",
"eslint": "^8.28.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-svelte": "^2.30.0",
"prettier": "^3.0.0",
"prettier-plugin-svelte": "^3.0.0",
"svelte": "^4.2.7",
"vite": "^4.4.2"
},
"type": "module",
"dependencies": {
"gamedig": "^4.3.0"
}
}

12
website/src/app.html Normal file
View File

@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@ -0,0 +1,7 @@
import { getAllServer } from '$lib/server/scripts/refreshGameservers.js';
getAllServer();
setInterval(() => {
getAllServer();
}, 3000);

View File

@ -0,0 +1,16 @@
<script>
import {onMount} from "svelte";
let data = {};
onMount(async () => {
const response = await fetch('/api/read-tournament');
data = await response.json();
});
</script>
<h2>Aktuelles Tunier</h2>
<h3>Diese Spiele werden Gespielt:</h3>
<p>{data.games}</p>
<h3>Die Teilnehmer lauten: </h3>
<p>{data.player}</p>

View File

@ -0,0 +1,28 @@
<script>
import { browser } from '$app/environment';
import ServerlistItem from './ServerlistItem.svelte';
const allServers = async () => {
if (browser) {
const response = await fetch('/api/serverlist');
const data = await response.json();
return data;
}
return [];
};
let promise = allServers();
</script>
<h1>Lokale Serverliste</h1>
<div>
{#await promise}
<p>Lade Daten...</p>
{:then servers}
{#each servers as serverdata}
<ServerlistItem server={serverdata} />
{/each}
{:catch error}
<p>Laden der Daten war nicht möglich!</p>
{/await}
</div>

View File

@ -0,0 +1,22 @@
<script>
export let server;
</script>
<div>
<table>
<tbody>
<tr>
<td>{server.game}</td>
<td>{server.name}</td>
</tr>
<tr>
<td>{server.playerCount.current}/{server.playerCount.max}</td>
<td>{server.status}</td>
</tr>
<tr>
<td>{server.ip}</td>
<td>{server.link}</td>
</tr>
</tbody>
</table>
</div>

View File

@ -0,0 +1,68 @@
import Gamedig from 'gamedig';
import fs from 'fs';
import { TEMP_FOLDER_LOCATION, CONFIG_FOLDER_LOCATION } from '$env/static/private';
export const getAllServer = async () => {
if (!fs.existsSync(TEMP_FOLDER_LOCATION)) fs.mkdirSync(TEMP_FOLDER_LOCATION, { recursive: true });
if (!fs.existsSync(CONFIG_FOLDER_LOCATION))
fs.mkdirSync(CONFIG_FOLDER_LOCATION, { recursive: true });
if (!fs.existsSync(CONFIG_FOLDER_LOCATION + 'gameserverlist.json')) return [];
const serverList = JSON.parse(fs.readFileSync(CONFIG_FOLDER_LOCATION + 'gameserverlist.json'));
let parsedResults = [];
for (const server of serverList) {
let status = true;
let serverResponse;
try {
serverResponse = await Gamedig.query({
type: server.type,
host: server.ip,
port: server.port
});
} catch (error) {
status = false;
}
let parsedResult;
if (status) {
parsedResult = {
game: server.type,
name: serverResponse.name,
playerCount: {
current: serverResponse.players.length,
max: serverResponse.maxplayers
},
status: status,
ip: server.ip,
link: ''
};
} else {
parsedResult = {
game: server.type,
name: server.name,
playerCount: {
current: 0,
max: 0
},
status: status,
ip: serverResponse.connect,
link: server.link
};
}
parsedResults.push(parsedResult);
}
fs.writeFile(
TEMP_FOLDER_LOCATION + 'gameserverstatus.json',
JSON.stringify(parsedResults),
(err) => {
if (err) throw err;
}
);
};

View File

View File

@ -0,0 +1,22 @@
<script>
import Serverlist from '$lib/client/components/Serverlist.svelte'
import CurrentTunier from "$lib/client/components/CurrentTunier.svelte";
let year = new Date().getFullYear();
</script>
<h1>Willkommen zur Weihnachtslan {year}</h1>
<table>
<tr>
<td>
<h2>aktive Server</h2>
<Serverlist />
</td>
<td>|</td>
<td>-----</td>
<td>|</td>
<td>
<h2>LAN Tunier</h2>
<CurrentTunier />
</td>
</tr>
</table>

View File

@ -0,0 +1,9 @@
import { promises as fs } from 'fs';
import path from 'path';
export async function GET() {
const filePath = path.resolve('static/tournament.json');
const data = await fs.readFile(filePath, 'utf8');
const json = JSON.parse(data);
return Response.json(json)
}

View File

@ -0,0 +1,14 @@
import fs from 'fs';
import { TEMP_FOLDER_LOCATION } from '$env/static/private';
export async function GET() {
if (!fs.existsSync(TEMP_FOLDER_LOCATION)) fs.mkdirSync(TEMP_FOLDER_LOCATION, { recursive: true });
if (!fs.existsSync(TEMP_FOLDER_LOCATION + 'gameserverstatus.json'))
return new Response({ status: 404 });
const serverList = fs.readFileSync(TEMP_FOLDER_LOCATION + 'gameserverstatus.json');
return new Response(serverList, { status: 200 });
}

View File

@ -0,0 +1,12 @@
import { promises as fs } from 'fs';
import path from 'path';
export async function POST({ request }) {
const data = await request.text();
try {
await fs.writeFile(path.resolve('static/tournament.json'), data);
return new Response('Array wurde gespeichert.');
} catch (err) {
console.error(err);
}
}

View File

View File

@ -0,0 +1,13 @@
<script>
import CurrentTunier from '$lib/client/components/CurrentTunier.svelte';
</script>
<h1>Tunier</h1>
<CurrentTunier />
<button
on:click={() => {
location.replace('/tunier/neu');
}}>Tunier erstellen</button
>

View File

View File

@ -0,0 +1,80 @@
<script>
let gameCount = 1;
let playerCount = 1;
let games = [];
let player = [];
function getGames() {
games = [];
const inputs = document.querySelectorAll('.Spiel');
inputs.forEach((input) => {
games.push(input.value);
});
return games;
}
function getPlayer() {
player = [];
const inputs = document.querySelectorAll('.Teilnehmer');
inputs.forEach((input) => {
player.push(input.value);
});
return player;
}
function addGame() {
gameCount++;
}
function removeGame() {
gameCount = gameCount > 1 ? gameCount - 1 : gameCount;
}
function addPlayer() {
playerCount++;
}
function removePlayer() {
playerCount = playerCount > 1 ? playerCount - 1 : playerCount;
}
async function addTournament() {
let gamelist = getGames();
let playerlist = getPlayer();
const tournament = {
games: gamelist,
player: playerlist
};
const res = await fetch('/api/update-json/', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(tournament)
});
if (res.ok) {
console.log('JSON gespeichert');
} else {
console.error('JSON nicht gespeichert');
}
}
</script>
<h1>Ein neues Tunier erstellen</h1>
<h2>Spiele hinzufügen</h2>
<ul>
{#each { length: gameCount } as _, i}
<li>{i + 1}. <input class="Spiel" type="text" /></li>
{/each}
</ul>
<button on:click={addGame}>noch ein Spiel hinzufügen</button>
<button on:click={removeGame}>letztes Spiel entfernen</button>
<h2>Teilnehmer hinzufügen</h2>
<ul>
{#each { length: playerCount } as _, i}
<li>{i + 1}.<input required class="Teilnehmer" type="text" /></li>{/each}
</ul>
<button on:click={addPlayer}>noch ein Teilnehmer hinzufügen</button>
<button on:click={removePlayer}>letzten Teilnehmer entfernen</button>
<h2>Tunier anlegen</h2>
<button on:click={addTournament}>Tunier anlegen</button>

BIN
website/static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

13
website/svelte.config.js Normal file
View File

@ -0,0 +1,13 @@
import adapter from '@sveltejs/adapter-auto';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter()
}
};
export default config;

6
website/vite.config.js Normal file
View File

@ -0,0 +1,6 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()]
});