Initial commit
This commit is contained in:
commit
7d1b869b86
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
2
discord-bot/.env.example
Normal file
2
discord-bot/.env.example
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
TOKEN=
|
||||||
|
CLIENT=
|
11
discord-bot/.eslintignore
Normal file
11
discord-bot/.eslintignore
Normal 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
|
12
discord-bot/.eslintrc.json
Normal file
12
discord-bot/.eslintrc.json
Normal 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
0
discord-bot/.gitignore
vendored
Normal file
11
discord-bot/.prettierignore
Normal file
11
discord-bot/.prettierignore
Normal 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
8
discord-bot/.prettierrc
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"useTabs": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"printWidth": 100,
|
||||||
|
"plugins": [],
|
||||||
|
"overrides": []
|
||||||
|
}
|
3
discord-bot/README.md
Normal file
3
discord-bot/README.md
Normal 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).
|
26
discord-bot/commands/generic/get.js
Normal file
26
discord-bot/commands/generic/get.js
Normal 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 });
|
||||||
|
}
|
36
discord-bot/commands/utility/login.example.js
Normal file
36
discord-bot/commands/utility/login.example.js
Normal 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);
|
||||||
|
}
|
6
discord-bot/commands/utility/ping.example.js
Normal file
6
discord-bot/commands/utility/ping.example.js
Normal 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 });
|
||||||
|
}
|
42
discord-bot/commands/utility/role.example.js
Normal file
42
discord-bot/commands/utility/role.example.js
Normal 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
71
discord-bot/deploy.js
Normal 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);
|
61
discord-bot/events/interactionCreate.js
Normal file
61
discord-bot/events/interactionCreate.js
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
7
discord-bot/events/ready.js
Normal file
7
discord-bot/events/ready.js
Normal 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
81
discord-bot/index.js
Normal 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
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
26
discord-bot/package.json
Normal 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
3
website/.env.example
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Private
|
||||||
|
TEMP_FOLDER_LOCATION=
|
||||||
|
CONFIG_FOLDER_LOCATION=
|
13
website/.eslintignore
Normal file
13
website/.eslintignore
Normal 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
15
website/.eslintrc.cjs
Normal 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
9
website/.gitignore
vendored
Normal 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
1
website/.npmrc
Normal file
@ -0,0 +1 @@
|
|||||||
|
engine-strict=true
|
13
website/.prettierignore
Normal file
13
website/.prettierignore
Normal 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
8
website/.prettierrc
Normal 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
1
website/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
# L Pa So
|
3019
website/package-lock.json
generated
Normal file
3019
website/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
27
website/package.json
Normal file
27
website/package.json
Normal 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
12
website/src/app.html
Normal 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>
|
7
website/src/hooks.server.js
Normal file
7
website/src/hooks.server.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { getAllServer } from '$lib/server/scripts/refreshGameservers.js';
|
||||||
|
|
||||||
|
getAllServer();
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
getAllServer();
|
||||||
|
}, 3000);
|
16
website/src/lib/client/components/CurrentTunier.svelte
Normal file
16
website/src/lib/client/components/CurrentTunier.svelte
Normal 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>
|
28
website/src/lib/client/components/Serverlist.svelte
Normal file
28
website/src/lib/client/components/Serverlist.svelte
Normal 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>
|
22
website/src/lib/client/components/ServerlistItem.svelte
Normal file
22
website/src/lib/client/components/ServerlistItem.svelte
Normal 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>
|
68
website/src/lib/server/scripts/refreshGameservers.js
Normal file
68
website/src/lib/server/scripts/refreshGameservers.js
Normal 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;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
0
website/src/routes/+page.js
Normal file
0
website/src/routes/+page.js
Normal file
22
website/src/routes/+page.svelte
Normal file
22
website/src/routes/+page.svelte
Normal 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>
|
9
website/src/routes/api/read-tournament/+server.js
Normal file
9
website/src/routes/api/read-tournament/+server.js
Normal 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)
|
||||||
|
}
|
14
website/src/routes/api/serverlist/+server.js
Normal file
14
website/src/routes/api/serverlist/+server.js
Normal 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 });
|
||||||
|
}
|
12
website/src/routes/api/update-json/+server.js
Normal file
12
website/src/routes/api/update-json/+server.js
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
0
website/src/routes/tunier/+page.js
Normal file
0
website/src/routes/tunier/+page.js
Normal file
13
website/src/routes/tunier/+page.svelte
Normal file
13
website/src/routes/tunier/+page.svelte
Normal 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
|
||||||
|
>
|
0
website/src/routes/tunier/neu/+page.js
Normal file
0
website/src/routes/tunier/neu/+page.js
Normal file
80
website/src/routes/tunier/neu/+page.svelte
Normal file
80
website/src/routes/tunier/neu/+page.svelte
Normal 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
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
13
website/svelte.config.js
Normal 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
6
website/vite.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [sveltekit()]
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user