Garde-Havoc/bot/cogs/spawner.py

318 lines
9.7 KiB
Python
Raw Normal View History

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 dataclasses import dataclass
from ipaddress import IPv4Address
import secrets
2024-12-18 22:17:51 +01:00
from mcrcon import MCRcon
2024-10-09 18:29:51 +02:00
import asyncio
2024-12-18 22:17:51 +01:00
from models.users import *
import utils
class RCON(MCRcon):
def __init__(self, ip: str, secret: str, port: int):
super().__init__(ip, secret, port)
def rconnect(self):
self.connect()
def whitelist(self):
pass
def sendcmd(self, cmds) -> None:
if isinstance(cmds, str):
return self.command(str)
if isinstance(cmds, list):
return [self.command(cmd) for cmd in cmds]
def __del__(self):
self.disconnect()
2024-10-09 18:29:51 +02:00
@dataclass
class Server:
container: None
name: str
2024-12-18 22:17:51 +01:00
rcon: RCON
2024-10-09 18:29:51 +02:00
port: int
players: int
# Global List of all running Containers
containers = list()
2024-12-18 22:17:51 +01:00
def get_server(name):
name = name.title()
for container in containers:
if container.name == name:
return container
return None
2024-10-09 18:29:51 +02:00
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():
2024-12-18 22:17:51 +01:00
'''
Generates a random minecraft seed
'''
2024-10-09 18:29:51 +02:00
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():
2024-12-18 22:17:51 +01:00
'''
Returns the next available IPv4 Port
'''
2024-10-09 18:29:51 +02:00
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
'''
2024-12-18 22:17:51 +01:00
try:
User.get(User.username == ctx.author.id)
except:
await ctx.send(f"{ctx.author.name} isn't registered!")
return
# Send user a Waiting screen to avoid confusion
2024-10-09 18:29:51 +02:00
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-12-18 22:17:51 +01:00
timestamp=utils.now()
2024-10-09 18:29:51 +02:00
)
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
2024-12-18 22:17:51 +01:00
# Set up needed variables
# Server Stuff
2024-10-09 18:29:51 +02:00
port = find_free_port()
server_name = server_name.title()
2024-12-18 22:17:51 +01:00
# Rcon Stuff
2024-10-09 18:29:51 +02:00
passwd = secrets.token_hex(32)
rcon_port = find_free_port()
2024-12-18 22:17:51 +01:00
# Image Enviroment
2024-10-09 18:29:51 +02:00
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",
"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": "",
}
2024-12-18 22:17:51 +01:00
# Decide between seed or world url if no ones given seed would be randomly generated
2024-10-09 18:29:51 +02:00
if not seed and not world_url:
seed = seed_generator()
if seed:
env["SEED"] = seed
if world_url:
env["WORLD"] = world_url
2024-12-18 22:17:51 +01:00
# setting up the container
2024-10-09 18:29:51 +02:00
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'}}
)
2024-12-18 22:17:51 +01:00
# Connect Container to the appropiate network
2024-10-09 18:29:51 +02:00
net = self.client.networks.get('bot_rcon')
net.connect(container)
2024-12-18 22:17:51 +01:00
# save container info
ip = self.client.containers.get(server_name).attrs['NetworkSettings']['Networks']['bot_rcon']['IPAddress']
rcon = RCON(ip, passwd, rcon_port)
server = Server(container, server_name, rcon, port, max_players)
2024-10-09 18:29:51 +02:00
containers.append(server)
2024-12-18 22:17:51 +01:00
# Send user the confirmation
2024-10-09 18:29:51 +02:00
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-12-18 22:17:51 +01:00
timestamp=utils.now()
2024-10-09 18:29:51 +02:00
)
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
'''
2024-12-18 22:17:51 +01:00
try:
User.get(User.username == ctx.author.id)
except:
await ctx.send(f"{ctx.author.name} isn't registered!")
return
2024-10-09 18:29:51 +02:00
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-12-18 22:17:51 +01:00
timestamp=utils.now()
2024-10-09 18:29:51 +02:00
)
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
'''
2024-12-18 22:17:51 +01:00
try:
User.get(User.username == ctx.author.id)
except:
await ctx.send(f"{ctx.author.name} isn't registered!")
return
2024-10-09 18:29:51 +02:00
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,
2024-12-18 22:17:51 +01:00
timestamp=utils.now()
2024-10-09 20:23:24 +02:00
)
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())