2024-10-09 18:29:51 +02:00
|
|
|
import discord
|
|
|
|
from discord.ext import commands
|
|
|
|
import docker
|
|
|
|
import random
|
|
|
|
import socket
|
|
|
|
from contextlib import closing
|
|
|
|
from datetime import datetime
|
|
|
|
import pytz
|
|
|
|
from dataclasses import dataclass
|
|
|
|
from ipaddress import IPv4Address
|
|
|
|
import secrets
|
|
|
|
import asyncio
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class Server:
|
|
|
|
container: None
|
|
|
|
name: str
|
|
|
|
ip: IPv4Address
|
|
|
|
port: int
|
|
|
|
players: int
|
|
|
|
rcon_pass: str
|
|
|
|
rcon_port: int
|
|
|
|
|
|
|
|
# Global List of all running Containers
|
|
|
|
containers = list()
|
|
|
|
|
2024-10-09 20:23:24 +02:00
|
|
|
color = discord.Color.from_rgb(13, 183, 237)
|
|
|
|
|
2024-10-09 18:29:51 +02:00
|
|
|
def seed_generator():
|
|
|
|
seed = random.randrange(1_000_000_000, 100_000_000_000_000)
|
|
|
|
if random.randrange(0,2) == 0:
|
|
|
|
seed *= -1
|
|
|
|
return str(seed)
|
|
|
|
|
|
|
|
def find_free_port():
|
|
|
|
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
|
|
|
|
s.bind(('', 0))
|
|
|
|
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
|
|
return s.getsockname()[1]
|
|
|
|
|
|
|
|
class Spawner(commands.Cog):
|
|
|
|
def __init__(self, bot: commands.Bot):
|
|
|
|
self.bot = bot
|
|
|
|
self.client = docker.from_env()
|
|
|
|
self.client.images.pull('itzg/minecraft-server:latest')
|
|
|
|
|
|
|
|
@commands.hybrid_command(name='spawn')
|
|
|
|
async def spawn(self,
|
|
|
|
ctx: commands.Context,
|
|
|
|
server_name: str,
|
|
|
|
world_url: str = None,
|
|
|
|
seed: str = None,
|
|
|
|
enable_command_blocks: bool = False,
|
|
|
|
max_players: int = 10,
|
|
|
|
enable_hardcore: bool = False,
|
|
|
|
):
|
|
|
|
'''
|
|
|
|
Spawns a standard defined Minecraft Server
|
|
|
|
Either from a World Download Link or a Seed
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
ctx: commands.Context
|
|
|
|
The context of the command invocation
|
|
|
|
server_name: str
|
|
|
|
Name of the Server
|
|
|
|
world_url: str
|
|
|
|
Download link of a minecraft world (Should be a downloadable ZIPP Archive
|
|
|
|
seed: str
|
|
|
|
Seed to generate a World from
|
|
|
|
enable_command_blocks: bool
|
|
|
|
Enable or disable command Block
|
|
|
|
max_players: int
|
|
|
|
Maximum Number of Players who can join the Server
|
|
|
|
enable_hardcore: bool
|
|
|
|
Enables Hardcore Minecraft
|
|
|
|
'''
|
|
|
|
embed = discord.Embed(
|
|
|
|
title="Starting Server",
|
|
|
|
description=f'''
|
|
|
|
Setting up {server_name}
|
|
|
|
|
|
|
|
This could take up to **5 minutes**
|
|
|
|
''',
|
2024-10-09 20:23:24 +02:00
|
|
|
color=color,
|
2024-10-09 18:29:51 +02:00
|
|
|
timestamp=datetime.now(pytz.timezone('Europe/Berlin'))
|
|
|
|
)
|
2024-10-09 20:23:24 +02:00
|
|
|
file = discord.File("../assets/clock.png", filename="clock.png")
|
|
|
|
embed.set_thumbnail(url="attachment://clock.png")
|
|
|
|
start = await ctx.send(file=file, embed=embed)
|
2024-10-09 18:29:51 +02:00
|
|
|
|
|
|
|
port = find_free_port()
|
|
|
|
server_name = server_name.title()
|
|
|
|
passwd = secrets.token_hex(32)
|
|
|
|
rcon_port = find_free_port()
|
|
|
|
|
|
|
|
env = {
|
|
|
|
"EULA": "true",
|
|
|
|
"TYPE": "FABRIC",
|
|
|
|
"VERSION": "1.21.1",
|
|
|
|
"SERVER_NAME": server_name,
|
|
|
|
"LEVEL": server_name,
|
|
|
|
"ONLINE_MODE": "true",
|
|
|
|
"TZ": "Europe/Berlin",
|
|
|
|
"MOTD": "\u00a7d\u00a7khhh\u00a76Powered by\u00a7b Garde Studios\u00a76!\u00a7d\u00a7khhh",
|
|
|
|
|
|
|
|
"OVERRIDE_SERVER_PROPERTIES": "true",
|
|
|
|
"ENABLE_COMMAND_BLOCK": enable_command_blocks,
|
|
|
|
"GAMEMODE": "survival",
|
|
|
|
"FORCE_GAMEMODE": "true",
|
|
|
|
|
|
|
|
"RCON_PASSWORD": passwd,
|
|
|
|
"RCON_PORT": rcon_port,
|
|
|
|
"BROADCAST_CONSOLE_TO_OPS": "false",
|
|
|
|
"BROADCAST_RCON_TO_OPS": "false",
|
|
|
|
"SERVER_PORT": port,
|
|
|
|
|
|
|
|
"FORCE_REDOWNLOAD": "true",
|
|
|
|
"INIT_MEMORY": "500M",
|
|
|
|
"MAX_MEMORY": "2G",
|
|
|
|
"USE_AIKAR_FLAGS": "true",
|
|
|
|
|
|
|
|
#"MODS_FILE": "/extras/mods.txt",
|
|
|
|
"OPS_FILE": "https://git.cyperpunk.de/Garde-Studios/Uno-MC/raw/branch/main/ops.json",
|
|
|
|
"SYNC_SKIP_NEWER_IN_DESTINATION": "false",
|
|
|
|
"MAX_PLAYERS": max_players,
|
|
|
|
|
|
|
|
"ANNOUNCE_PLAYER_ACHIEVMENTS": "true",
|
|
|
|
"HARDCORE": enable_hardcore,
|
|
|
|
|
|
|
|
"SNOOPER_ENABLED": "false",
|
|
|
|
"SPAWN_PROTECTION": 0,
|
|
|
|
"VIEW_DISTANCE": 12,
|
|
|
|
"ALLOW_FLIGHT": "false",
|
|
|
|
|
|
|
|
# "RESOURCE_PACK": "",
|
|
|
|
# "RESOURCE_PACK_SHA1": "",
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if not seed and not world_url:
|
|
|
|
seed = seed_generator()
|
|
|
|
if seed:
|
|
|
|
env["SEED"] = seed
|
|
|
|
if world_url:
|
|
|
|
env["WORLD"] = world_url
|
|
|
|
|
|
|
|
container = self.client.containers.run(
|
|
|
|
image='itzg/minecraft-server:latest',
|
|
|
|
environment=env,
|
|
|
|
detach=True,
|
|
|
|
hostname=server_name,
|
|
|
|
name=server_name,
|
|
|
|
network_mode='bridge',
|
|
|
|
ports={port:port, rcon_port:rcon_port},
|
|
|
|
restart_policy={"Name": "always"},
|
|
|
|
volumes={'mods.txt': {'bind': '/extras/mods.txt', 'mode': 'ro'}}
|
|
|
|
)
|
|
|
|
|
|
|
|
net = self.client.networks.get('bot_rcon')
|
|
|
|
net.connect(container)
|
|
|
|
|
|
|
|
ip = self.client.containers.get(server_name).attrs['NetworkSettings']['Networks']['bot_rcon']['IPAddress']
|
|
|
|
server = Server(container, server_name, IPv4Address(ip), port, max_players, passwd, rcon_port)
|
|
|
|
|
|
|
|
containers.append(server)
|
|
|
|
|
|
|
|
embed = discord.Embed(
|
|
|
|
title="Success",
|
|
|
|
description=f'''
|
|
|
|
**{server_name}** has started!
|
|
|
|
|
|
|
|
**Connection URL**:
|
|
|
|
garde-studios.de:{port}
|
|
|
|
''',
|
2024-10-09 20:23:24 +02:00
|
|
|
color=color,
|
2024-10-09 18:29:51 +02:00
|
|
|
timestamp=datetime.now(pytz.timezone('Europe/Berlin'))
|
|
|
|
)
|
2024-10-09 20:23:24 +02:00
|
|
|
file = discord.File("../assets/docker.png", filename="docker.png")
|
|
|
|
embed.set_thumbnail(url="attachment://docker.png")
|
2024-10-09 18:29:51 +02:00
|
|
|
await start.delete()
|
2024-10-09 20:23:24 +02:00
|
|
|
await ctx.send(file=file, embed=embed)
|
2024-10-09 18:29:51 +02:00
|
|
|
|
|
|
|
@commands.hybrid_command(name='servers')
|
|
|
|
async def servers(self, ctx: commands.Context):
|
|
|
|
'''
|
|
|
|
List all currently Running Servers
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
ctx: commands.Context
|
|
|
|
The context of the command invocation
|
|
|
|
'''
|
|
|
|
|
|
|
|
embed = discord.Embed(
|
|
|
|
title="Currently Running Servers",
|
|
|
|
description="List of all currently running Minecraft Servers",
|
2024-10-09 20:23:24 +02:00
|
|
|
color=color,
|
2024-10-09 18:29:51 +02:00
|
|
|
timestamp=datetime.now(pytz.timezone('Europe/Berlin'))
|
|
|
|
)
|
|
|
|
|
|
|
|
for container in containers:
|
|
|
|
desc = f'''
|
|
|
|
*Status*: {container.container.status}
|
|
|
|
*URL*: garde-studios.de:{container.port}
|
|
|
|
'''
|
|
|
|
embed.add_field(name=f'{container.name} 0/{container.players}', value=desc)
|
2024-10-09 20:23:24 +02:00
|
|
|
|
|
|
|
file = discord.File("../assets/cloud.png", filename="cloud.png")
|
|
|
|
embed.set_thumbnail(url="attachment://cloud.png")
|
|
|
|
|
|
|
|
await ctx.send(file=file, embed=embed)
|
2024-10-09 18:29:51 +02:00
|
|
|
|
|
|
|
@commands.hybrid_command(name='kill')
|
|
|
|
async def kill(self, ctx: commands.Context, server_name: str):
|
|
|
|
'''
|
|
|
|
Kill & remove currently Running Servers
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
ctx: commands.Context
|
|
|
|
The context of the command invocation
|
|
|
|
server_name: str
|
|
|
|
Name of the server that should be removed
|
|
|
|
'''
|
|
|
|
|
|
|
|
server_name = server_name.title()
|
|
|
|
|
|
|
|
# Check if Server exist
|
|
|
|
rm = str()
|
|
|
|
conn = None
|
|
|
|
for container in containers:
|
|
|
|
if container.name == server_name:
|
|
|
|
rm = server_name
|
|
|
|
conn = container
|
|
|
|
break
|
|
|
|
|
|
|
|
if not rm:
|
|
|
|
await ctx.send("---Server not found---")
|
|
|
|
return
|
|
|
|
|
|
|
|
conn.container.remove(force=True)
|
|
|
|
#self.client.volumes.get(server_name).remove()
|
|
|
|
containers.remove(conn)
|
|
|
|
|
2024-10-09 20:23:24 +02:00
|
|
|
embed = discord.Embed(
|
|
|
|
title="Killed",
|
|
|
|
description=f"{server_name} killed!",
|
|
|
|
color=color,
|
|
|
|
timestamp=datetime.now(pytz.timezone('Europe/Berlin'))
|
|
|
|
)
|
|
|
|
|
|
|
|
file = discord.File("../assets/rip.png", filename="rip.png")
|
|
|
|
embed.set_thumbnail(url="attachment://rip.png")
|
|
|
|
|
|
|
|
await ctx.send(file=file, embed=embed)
|
2024-10-09 18:29:51 +02:00
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
for _ in range(10):
|
|
|
|
print("|", seed_generator(), "|")
|
|
|
|
print("Port:", find_free_port())
|