Garde-Havoc/bot/cogs/spawner.py
2024-10-09 18:29:51 +02:00

243 lines
7.5 KiB
Python

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()
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**
''',
color=discord.Color.random(),
timestamp=datetime.now(pytz.timezone('Europe/Berlin'))
)
start = await ctx.send(embed=embed)
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}
''',
color=discord.Color.random(),
timestamp=datetime.now(pytz.timezone('Europe/Berlin'))
)
await start.delete()
await ctx.send(embed=embed)
@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",
color=discord.Color.random(),
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)
await ctx.send(embed=embed)
@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)
await ctx.send(f"Server {server_name} killed successfully")
if __name__ == '__main__':
for _ in range(10):
print("|", seed_generator(), "|")
print("Port:", find_free_port())