From 8b1b6fdd54bbb1f1abf69900ed4e3672993c3b00 Mon Sep 17 00:00:00 2001 From: DerGrumpf Date: Wed, 18 Dec 2024 22:17:51 +0100 Subject: [PATCH] Changed: Final --- bot/assets/death.png | Bin 0 -> 6356 bytes bot/assets/end.png | Bin 0 -> 5446 bytes bot/assets/fight.png | Bin 0 -> 5363 bytes bot/assets/init.png | Bin 0 -> 8540 bytes bot/assets/safe.png | Bin 0 -> 5359 bytes bot/bot.db | Bin 0 -> 20480 bytes bot/cogs/minecraft.py | 217 +++++++++++++++++++----------------- bot/cogs/spawner.py | 82 +++++++++++--- bot/cogs/user_management.py | 169 ++++++++++++++++++++++++++-- bot/models/base.py | 10 ++ bot/models/containers.py | 13 +++ bot/models/users.py | 12 ++ bot/requirements.txt | 7 +- bot/utils.py | 7 ++ 14 files changed, 393 insertions(+), 124 deletions(-) create mode 100644 bot/assets/death.png create mode 100644 bot/assets/end.png create mode 100644 bot/assets/fight.png create mode 100644 bot/assets/init.png create mode 100644 bot/assets/safe.png create mode 100644 bot/bot.db create mode 100644 bot/models/base.py create mode 100644 bot/models/containers.py create mode 100644 bot/models/users.py create mode 100644 bot/utils.py diff --git a/bot/assets/death.png b/bot/assets/death.png new file mode 100644 index 0000000000000000000000000000000000000000..91d2e77e8451d0b140689a812fd7cbafe2277016 GIT binary patch literal 6356 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 P1&*@3hFtl3v!MS6SNyft literal 0 HcmV?d00001 diff --git a/bot/assets/end.png b/bot/assets/end.png new file mode 100644 index 0000000000000000000000000000000000000000..0ee85502857012f10e598099be5483ac8588a8a3 GIT binary patch literal 5446 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 BsD%Ik literal 0 HcmV?d00001 diff --git a/bot/assets/init.png b/bot/assets/init.png new file mode 100644 index 0000000000000000000000000000000000000000..5d50a81f7aa6ffd955bc6bb031515afddebb0488 GIT binary patch literal 8540 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 literal 0 HcmV?d00001 diff --git a/bot/bot.db b/bot/bot.db new file mode 100644 index 0000000000000000000000000000000000000000..fd1cfc91ced4f26285002ef163a5b7bc40b6121d GIT binary patch literal 20480 zcmeI(O>Wvi6bJB`7>cMA=mxQ!xT+eYCYmwEak7dVtQ7H)#x+$~RLH@0WvP$|jMPmR zQBTk#^aQ<7PS9me&@msT6-}~?kp53L9u3b=^L}291s~2^Q5>-FYCQB}RwXY8rR10~ zLP&OT%F8S=@-sP?L6?IEu356*{IM+)G6jVc^Mzl9o*W<`009U<00Izz00bZa0SG|g zDFociv+}DF{T7Y<;Ole}jQhhue>#o)WSl*5Y|FJ-x6?T9+N{y3+ZRmzJ4TP^n9X|ye;$C`HU%{+D9 z$w$jMd1pDhTCG+|vt@>~dNd(<4l28^LNw`n{xBM?FGiLh2cM%!Joe(~YSj0=Sen-5 zqjVc>J6%O3*_+eiPa+Ql1Rwwb2tWV=5P$##AOHafKmYigQ^LC6Q()N39$HG_x38|a1?>WAFZxTfz3z9$SOsz 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() - class States(enum.Enum): NOTHING = 0 INIT = 1 @@ -41,7 +24,6 @@ class States(enum.Enum): class Minecraft(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot - self.servers = dict() transitions = [ ['init_game', States.NOTHING, States.INIT], @@ -68,21 +50,12 @@ class Minecraft(commands.Cog): server_name: str Server on which the Session should be initialized """ - server_name = server_name.title() - - c = None - for container in containers: - if server_name == container.name: - c = container - break + c = get_server(server_name) if not c: await ctx.send("---The server doesn't run---") return - conn = RCON(str(c.ip), c.rcon_pass, c.rcon_port) - self.servers[server_name] = conn - cmds = [ "/effect give @a minecraft:resistance infinite 255 true", "/effect give @a minecraft:saturation infinite 4 true", @@ -92,9 +65,29 @@ class Minecraft(commands.Cog): "/worldborder set 5", "/whitelist off" ] + c.rcon.rconnect() + c.rcon.sendcmd(cmds) - conn.sendcmd(cmds) - await ctx.send("init Border Wars Game") + embed = discord.Embed( + title=f"Border Wars @ {server_name.title()}", + description=''' + Session initialized + Get ready to get a good armorset. + + **Explanation** + This phase last as long as the moderator wants. + Every player is immune to damage and can't die by hunger. + Look out for a good direction to scout. + + Happy Border Wars! + ''', + color=color, + timestamp=utils.now() + ) + file = discord.File("../assets/init.png", filename="init.png") + embed.set_thumbnail(url="attachment://init.png") + + await ctx.send(file=file, embed=embed) @commands.hybrid_command(name='safe') async def safe(self, ctx: commands.Context, server_name: str): @@ -108,25 +101,12 @@ class Minecraft(commands.Cog): server_name: str Server on which the Session should be initialized """ - server_name = server_name.title() + c = get_server(server_name) - c = None - for container in containers: - if server_name == container.name: - c = container - break - if not c: await ctx.send("---The server doesn't run---") return - - conn = self.servers.get(server_name) - - if not conn: - await ctx.send("---Border Wars Session not Initialized---") - return - - + cmds = [ '''/title @a subtitle ["",{"text":"bei ","color":"blue"},{"text":"BORDER WARS!","bold":true,"color":"red"}]''', '''/title @a title {"text":"Viel Glück!","bold":true,"color":"blue"}''', @@ -137,9 +117,29 @@ class Minecraft(commands.Cog): "/effect clear @a" ] - conn.sendcmd(cmds) - await ctx.send("Switched to Safe Phase") - + c.rcon.sendcmd(cmds) + + embed = discord.Embed( + title=f"Border Wars @ {server_name.title()}", + description=''' + Switched to Safe Phase + Hide from other players and scout for resources. + + **Explanation** + This phase lasts 30 minutes, after that the fighting phase begins. + Every player who dies will keep there inventory. + Get a good armorset and be ready to fight. + + Happy Border Wars! + ''', + color=color, + timestamp=utils.now() + ) + file = discord.File("../assets/safe.png", filename="safe.png") + embed.set_thumbnail(url="attachment://safe.png") + + await ctx.send(file=file, embed=embed) + @commands.hybrid_command(name='fight') async def fight(self, ctx: commands.Context, server_name: str): """ @@ -152,25 +152,12 @@ class Minecraft(commands.Cog): server_name: str Server on which the Session should be initialized """ - server_name = server_name.title() - - c = None - for container in containers: - if server_name == container.name: - c = container - break + c = get_server(server_name) if not c: await ctx.send("---The server doesn't run---") return - conn = self.servers.get(server_name) - - if not conn: - await ctx.send("---Border Wars Session not Initialized---") - return - - cmds = [ '''/title @a subtitle {"text":"ÜBERLEBEN!","bold":true,"color":"red"}''', '''/title @a title {"text":"Möge der beste","color":"blue"}''', @@ -179,8 +166,28 @@ class Minecraft(commands.Cog): "/gamerule keepInventory false" ] - conn.sendcmd(cmds) - await ctx.send("Switched to Fight Phase") + c.rcon.sendcmd(cmds) + + embed = discord.Embed( + title=f"Border Wars @ {server_name.title()}", + description=''' + Switched to Fight Phase + Now the real fun begins. + + **Explanation** + This phase lasts 60 minutes, after that the game ends. + Every player can die now and therefor will lose. + Look out for other players. + + Happy Border Wars! + ''', + color=color, + timestamp=utils.now() + ) + file = discord.File("../assets/fight.png", filename="fight.png") + embed.set_thumbnail(url="attachment://fight.png") + + await ctx.send(file=file, embed=embed) @commands.hybrid_command(name='death') async def death(self, ctx: commands.Context, server_name: str): @@ -194,33 +201,40 @@ class Minecraft(commands.Cog): server_name: str Server on which the Session should be initialized """ - server_name = server_name.title() + c = get_server(server_name) - c = None - for container in containers: - if server_name == container.name: - c = container - break - if not c: await ctx.send("---The server doesn't run---") return - conn = self.servers.get(server_name) - - if not conn: - await ctx.send("---Border Wars Session not Initialized---") - return - - cmds = [ '''/title @a title ["",{"text":"Sudden ","color":"dark_blue"},{"text":"DEATH!","bold":true,"color":"red"}]''', "/playsound minecraft:entity.ender_dragon.growl ambient @a 0 64 0 80", "/worldborder set 5 600" ] - conn.sendcmd(cmds) - await ctx.send("Switched to Sudden Death Phase") + c.rcon.sendcmd(cmds) + + embed = discord.Embed( + title=f"Border Wars @ {server_name.title()}", + description=''' + Switched to Sudden Death + Only one can be the Winner. + + **Explanation** + This phase lasts as long as only one player survives. + The worldborder will now shrink 'till zero zero. + Good Luck. + + Happy Border Wars! + ''', + color=color, + timestamp=utils.now() + ) + file = discord.File("../assets/death.png", filename="death.png") + embed.set_thumbnail(url="attachment://death.png") + + await ctx.send(file=file, embed=embed) @commands.hybrid_command(name='end') async def end(self, ctx: commands.Context, server_name: str, playername: str): @@ -236,24 +250,11 @@ class Minecraft(commands.Cog): playername: str Player which is announced as the Winner """ - server_name = server_name.title() - - c = None - for container in containers: - if server_name == container.name: - c = container - break + c = get_server(server_name) if not c: await ctx.send("---The server doesn't run---") return - - conn = self.servers.get(server_name) - - if not conn: - await ctx.send("---Border Wars Session not Initialized---") - return - cmds = [ "/worldborder center 0 0", @@ -264,8 +265,20 @@ class Minecraft(commands.Cog): "/playsound minecraft:entity.ender_dragon_death ambient @a 0 64 0 80", ] - conn.sendcmd(cmds) - await ctx.send("Ended Border Wars Session") + c.rcon.sendcmd(cmds) + + embed = discord.Embed( + title=f"Border Wars @ {server_name.title()}", + description=f''' + *Game Over* + Congratulations **{playername}**. + + Hope you had fun @ **Border Wars!** + ''', + color=color, + timestamp=utils.now() + ) + file = discord.File("../assets/end.png", filename="end.png") + embed.set_thumbnail(url="attachment://end.png") - - + await ctx.send(file=file, embed=embed) diff --git a/bot/cogs/spawner.py b/bot/cogs/spawner.py index c779b33..9c35089 100644 --- a/bot/cogs/spawner.py +++ b/bot/cogs/spawner.py @@ -4,35 +4,65 @@ 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 +from mcrcon import MCRcon import asyncio +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() @dataclass class Server: container: None name: str - ip: IPv4Address + rcon: RCON port: int players: int - rcon_pass: str - rcon_port: int # Global List of all running Containers containers = list() +def get_server(name): + name = name.title() + for container in containers: + if container.name == name: + return container + return None color = discord.Color.from_rgb(13, 183, 237) def seed_generator(): + ''' + Generates a random minecraft seed + ''' 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(): + ''' + Returns the next available IPv4 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) @@ -75,6 +105,13 @@ class Spawner(commands.Cog): enable_hardcore: bool Enables Hardcore Minecraft ''' + 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 embed = discord.Embed( title="Starting Server", description=f''' @@ -83,17 +120,21 @@ class Spawner(commands.Cog): This could take up to **5 minutes** ''', color=color, - timestamp=datetime.now(pytz.timezone('Europe/Berlin')) + timestamp=utils.now() ) file = discord.File("../assets/clock.png", filename="clock.png") embed.set_thumbnail(url="attachment://clock.png") start = await ctx.send(file=file, embed=embed) + # Set up needed variables + # Server Stuff port = find_free_port() server_name = server_name.title() + # Rcon Stuff passwd = secrets.token_hex(32) rcon_port = find_free_port() + # Image Enviroment env = { "EULA": "true", "TYPE": "FABRIC", @@ -120,7 +161,6 @@ class Spawner(commands.Cog): "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, @@ -138,6 +178,7 @@ class Spawner(commands.Cog): } + # Decide between seed or world url if no ones given seed would be randomly generated if not seed and not world_url: seed = seed_generator() if seed: @@ -145,6 +186,7 @@ class Spawner(commands.Cog): if world_url: env["WORLD"] = world_url + # setting up the container container = self.client.containers.run( image='itzg/minecraft-server:latest', environment=env, @@ -157,14 +199,18 @@ class Spawner(commands.Cog): volumes={'mods.txt': {'bind': '/extras/mods.txt', 'mode': 'ro'}} ) + # Connect Container to the appropiate network 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) + # 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) containers.append(server) - + + # Send user the confirmation embed = discord.Embed( title="Success", description=f''' @@ -174,7 +220,7 @@ class Spawner(commands.Cog): garde-studios.de:{port} ''', color=color, - timestamp=datetime.now(pytz.timezone('Europe/Berlin')) + timestamp=utils.now() ) file = discord.File("../assets/docker.png", filename="docker.png") embed.set_thumbnail(url="attachment://docker.png") @@ -191,12 +237,17 @@ class Spawner(commands.Cog): ctx: commands.Context The context of the command invocation ''' + try: + User.get(User.username == ctx.author.id) + except: + await ctx.send(f"{ctx.author.name} isn't registered!") + return embed = discord.Embed( title="Currently Running Servers", description="List of all currently running Minecraft Servers", color=color, - timestamp=datetime.now(pytz.timezone('Europe/Berlin')) + timestamp=utils.now() ) for container in containers: @@ -223,6 +274,11 @@ class Spawner(commands.Cog): server_name: str Name of the server that should be removed ''' + try: + User.get(User.username == ctx.author.id) + except: + await ctx.send(f"{ctx.author.name} isn't registered!") + return server_name = server_name.title() @@ -247,7 +303,7 @@ class Spawner(commands.Cog): title="Killed", description=f"{server_name} killed!", color=color, - timestamp=datetime.now(pytz.timezone('Europe/Berlin')) + timestamp=utils.now() ) file = discord.File("../assets/rip.png", filename="rip.png") diff --git a/bot/cogs/user_management.py b/bot/cogs/user_management.py index 5283d20..8d4ba66 100644 --- a/bot/cogs/user_management.py +++ b/bot/cogs/user_management.py @@ -1,12 +1,165 @@ +import discord from discord.ext import commands -from sqlalchemy import create_engine -from sqlalchemy.orm import DeclarativeBase +from models.users import * +from mojang import API +import utils -#engine = create_engine("sqlite://user.sqlite", echo=True) -#connection = engine.connect() - -class User(DeclarativeBase): - pass +color = discord.Color.from_rgb(79, 227, 119) class UserManager(commands.Cog): - pass + def __init__(self, bot: commands.Bot): + self.bot = bot + + def gen_user_info(self, user_name: str, user_id: int): + ''' + Generates user output Embed + + Parameters + ---------- + user_name: str + given user name + user_id: int + discord user id to access database info + ''' + user = User.get(User.username == user_id) + embed = discord.Embed( + title=user_name if not user.is_admin else f'{user_name} (Admin)', + description=f''' + Name: {user.mc_name} + UUID: {user.mc_uuid} + Registered since {user.registration_date.strftime("%d.%m.%Y, %H:%M:%S")} + ''', + color=color, + timestamp=utils.now() + ) + return embed + + @commands.hybrid_command(name='info') + async def info(self, ctx: commands.Context): + ''' + Registers Users to internal Database + and links there minecraft username to there discord account. + + Parameters + ---------- + ctx: commands.Context + The context of the command invocation + ''' + try: + user = User.get(User.username == ctx.author.id) + await ctx.send(embed=self.gen_user_info(ctx.author.name, ctx.author.id)) + except: + await ctx.send(f"{ctx.author.name} isn't registered") + + @commands.hybrid_command(name='register') + async def register(self, ctx: commands.Context, minecraft_name: str): + ''' + Registers Users to internal Database + and links there minecraft username to there discord account. + + Parameters + ---------- + ctx: commands.Context + The context of the command invocation + minecraft_name: str + The minecraft user name to link with + ''' + # Get minecraft uuid + api = API() + uuid = api.get_uuid(minecraft_name) + if not uuid: + await ctx.send("Username doesn't exist") + return + + # build user + try: + user = User( + username=ctx.author.id, + mc_name=minecraft_name, + mc_uuid=uuid, + is_admin=False if not ctx.author.id == 418848241036165160 else True + ) + user.save() + except IntegrityError: + await ctx.send("You're already registered") + return + + await ctx.send(embed=self.gen_user_info(ctx.author.name, ctx.author.id)) + + @commands.hybrid_command(name='delete') + async def delete(self, ctx: commands.Context): + ''' + Registers Users to internal Database + and links there minecraft username to there discord account. + + Parameters + ---------- + ctx: commands.Context + The context of the command invocation + ''' + try: + user = User.get(User.username == ctx.author.id) + user.delete_instance() + await ctx.send(f"Purged {ctx.author.name} from database!") + except: + await ctx.send(f"{ctx.author.name} isn't registered!") + + @commands.hybrid_command(name='op') + async def op(self, ctx: commands.Context, member: discord.Member): + ''' + Toggels if User is an Admin + + Parameters + ---------- + ctx: commands.Context + The context of the command invocation + ''' + user = User.get(User.username == ctx.author.id) + if not user.is_admin: + await ctx.send("You're not allowed to use this command") + return + + if member.id == 418848241036165160: + await ctx.send(f"{member.name} is always an admin") + return + + user = User.get(User.username == member.id) + user.is_admin = not user.is_admin + user.save() + await ctx.send(embed=self.gen_user_info(member.name, member.id)) + + + @commands.hybrid_command(name='all') + async def all(self, ctx: commands.Context): + ''' + Returns User info + + Parameters + ---------- + ctx: commands.Context + The context of the command invocation + ''' + try: + user = User.get(User.username == ctx.author.id) + if not user.is_admin: + await ctx.send("You're not allowed to use this command") + return + except: + await ctx.send(f"{ctx.author.name} isn't registered") + return + + + embed = discord.Embed( + title="All Users", + description="Registered Users in Database", + color=color, + timestamp=utils.now() + ) + + rows = User.select() + for row in rows: + member = await ctx.guild.fetch_member(row.username) + member = member if not row.is_admin else f'{member} (Admin)' + reg_date = row.registration_date.strftime("%d.%m.%Y, %H:%M:%S") + embed.add_field(name=member, value="{}\n{}\n{}\n".format(row.mc_name, row.mc_uuid, reg_date)) + await ctx.send(embed=embed) diff --git a/bot/models/base.py b/bot/models/base.py new file mode 100644 index 0000000..ecd1b90 --- /dev/null +++ b/bot/models/base.py @@ -0,0 +1,10 @@ +from peewee import * +from playhouse.postgres_ext import * +import os +import datetime + +db = SqliteDatabase('bot.db') + +class BaseModel(Model): + class Meta: + database = db diff --git a/bot/models/containers.py b/bot/models/containers.py new file mode 100644 index 0000000..45de73a --- /dev/null +++ b/bot/models/containers.py @@ -0,0 +1,13 @@ +from peewee import * +from models.base import BaseModel +import datetime + +class Container(BaseModel): + # Container + name = + # players = + port = + + # RCON + rcon_port = + rcon_pwd = diff --git a/bot/models/users.py b/bot/models/users.py new file mode 100644 index 0000000..df0b1b9 --- /dev/null +++ b/bot/models/users.py @@ -0,0 +1,12 @@ +from peewee import * +from models.base import BaseModel +import datetime + +class User(BaseModel): + username = CharField(unique=True) + mc_name = CharField(unique=True) + mc_uuid = CharField(unique=True) + is_admin = BooleanField(default=False) + registration_date = DateTimeField(default=datetime.datetime.now()) + +User.create_table() diff --git a/bot/requirements.txt b/bot/requirements.txt index a6dbfe8..193aae9 100644 --- a/bot/requirements.txt +++ b/bot/requirements.txt @@ -11,13 +11,18 @@ docker==7.1.0 frozenlist==1.4.1 greenlet==3.1.1 idna==3.10 +Jinja2==3.1.4 +MarkupSafe==3.0.1 mcrcon==0.7.0 +mojang==1.1.0 multidict==6.1.0 +peewee==3.17.7 +psycopg2-binary==2.9.9 +Pygments==2.18.0 python-dotenv==1.0.1 pytz==2024.2 requests==2.32.3 six==1.16.0 -SQLAlchemy==2.0.35 transitions==0.9.2 typing_extensions==4.12.2 urllib3==2.2.3 diff --git a/bot/utils.py b/bot/utils.py new file mode 100644 index 0000000..78235ce --- /dev/null +++ b/bot/utils.py @@ -0,0 +1,7 @@ +import datetime +import pytz + +now = lambda: datetime.datetime.now(pytz.timezone('Europe/Berlin')) + +if __name__ == '__main__': + print(now())