v0.1
commit
a670191b31
@ -0,0 +1,10 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# contain secrets
|
||||
*.env
|
||||
application/mc-auth.config
|
||||
|
||||
# test-env data
|
||||
test-env/data
|
||||
test-env/config
|
@ -0,0 +1,23 @@
|
||||
FROM python:3.13-slim
|
||||
|
||||
# User setup
|
||||
RUN adduser -u 1001 python_user
|
||||
|
||||
RUN mkdir /home/python_user/app && chown python_user:python_user /home/python_user/app
|
||||
WORKDIR /home/python_user/app/
|
||||
|
||||
# Copy application files
|
||||
COPY ./application /home/python_user/app
|
||||
RUN rm -rf /home/python_user/app/test-env \
|
||||
/home/python_user/app/.venv \
|
||||
/home/python_user/app/mc-auth.config
|
||||
RUN chown -R python_user:python_user /home/python_user/app
|
||||
|
||||
USER python_user
|
||||
|
||||
# Install python requirements
|
||||
#COPY ./requirements.txt ./
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
|
||||
CMD ["python", "start.py"]
|
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/application/.venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.13 virtualenv at ~/Software_Projects/DiscordMinecraftAuthenticator/application/.venv" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
@ -0,0 +1,24 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="PyCompatibilityInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ourVersions">
|
||||
<value>
|
||||
<list size="6">
|
||||
<item index="0" class="java.lang.String" itemvalue="3.12" />
|
||||
<item index="1" class="java.lang.String" itemvalue="3.6" />
|
||||
<item index="2" class="java.lang.String" itemvalue="3.13" />
|
||||
<item index="3" class="java.lang.String" itemvalue="3.11" />
|
||||
<item index="4" class="java.lang.String" itemvalue="3.10" />
|
||||
<item index="5" class="java.lang.String" itemvalue="3.7" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ignoredPackages">
|
||||
<list />
|
||||
</option>
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.13 virtualenv at ~/Software_Projects/DiscordMinecraftAuthenticator/.venv" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 virtualenv at ~/Software_Projects/DiscordMinecraftAuthenticator/application/.venv" project-jdk-type="Python SDK" />
|
||||
<component name="PythonCompatibilityInspectionAdvertiser">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
</project>
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/DiscordMinecraftAuthenticator.iml" filepath="$PROJECT_DIR$/.idea/DiscordMinecraftAuthenticator.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
@ -0,0 +1,189 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AutoImportSettings">
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="14eb3365-c838-47ac-baf5-458c1d4e585a" name="Changes" comment="">
|
||||
<change afterPath="$PROJECT_DIR$/config/__init__.py" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/config/config.py" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/database/DB.py" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/database/__init__.py" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/discord_bot/__init__.py" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/discord_bot/discord_bot.py" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/log/Log.py" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/log/__init__.py" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/mc-auth.config" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/minecraft_interface/MinecraftAPI.py" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/minecraft_interface/MinecraftServerAPI.py" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/minecraft_interface/__init__.py" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/start.py" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/version.py" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="FileTemplateManagerImpl">
|
||||
<option name="RECENT_TEMPLATES">
|
||||
<list>
|
||||
<option value="Python Script" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
|
||||
</component>
|
||||
<component name="ProjectColorInfo">{
|
||||
"associatedIndex": 3
|
||||
}</component>
|
||||
<component name="ProjectId" id="339KQHi06fLjqrzJLXr3nWRWy96" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">{
|
||||
"keyToString": {
|
||||
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||
"Python.DB.executor": "Run",
|
||||
"Python.MinecraftAPI.executor": "Debug",
|
||||
"Python.MinecraftServerAPI.executor": "Run",
|
||||
"Python.start.executor": "Run",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"git-widget-placeholder": "main",
|
||||
"last_opened_file_path": "/home/peery/Software_Projects/DiscordMinecraftAuthenticator/application",
|
||||
"settings.editor.selected.configurable": "project.propVCSSupport.DirectoryMappings"
|
||||
}
|
||||
}</component>
|
||||
<component name="RecentsManager">
|
||||
<key name="MoveFile.RECENT_KEYS">
|
||||
<recent name="$PROJECT_DIR$/application" />
|
||||
<recent name="$PROJECT_DIR$" />
|
||||
</key>
|
||||
</component>
|
||||
<component name="RunManager" selected="Python.start">
|
||||
<configuration name="DB" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
|
||||
<module name="DiscordMinecraftAuthenticator" />
|
||||
<option name="ENV_FILES" value="$PROJECT_DIR$/postgres-secrets.env" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONUNBUFFERED" value="1" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="IS_MODULE_SDK" value="true" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/database/DB.py" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||
<option name="EMULATE_TERMINAL" value="false" />
|
||||
<option name="MODULE_MODE" value="false" />
|
||||
<option name="REDIRECT_INPUT" value="false" />
|
||||
<option name="INPUT_FILE" value="" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="MinecraftAPI" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
|
||||
<module name="DiscordMinecraftAuthenticator" />
|
||||
<option name="ENV_FILES" value="" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONUNBUFFERED" value="1" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="IS_MODULE_SDK" value="true" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/minecraft_interface/MinecraftAPI.py" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||
<option name="EMULATE_TERMINAL" value="false" />
|
||||
<option name="MODULE_MODE" value="false" />
|
||||
<option name="REDIRECT_INPUT" value="false" />
|
||||
<option name="INPUT_FILE" value="" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="MinecraftServerAPI" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
|
||||
<module name="DiscordMinecraftAuthenticator" />
|
||||
<option name="ENV_FILES" value="$PROJECT_DIR$/minecraft-secrets.env" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONUNBUFFERED" value="1" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="IS_MODULE_SDK" value="true" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/minecraft_interface/MinecraftServerAPI.py" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||
<option name="EMULATE_TERMINAL" value="false" />
|
||||
<option name="MODULE_MODE" value="false" />
|
||||
<option name="REDIRECT_INPUT" value="false" />
|
||||
<option name="INPUT_FILE" value="" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="start" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
|
||||
<module name="DiscordMinecraftAuthenticator" />
|
||||
<option name="ENV_FILES" value="$PROJECT_DIR$/base.env:$PROJECT_DIR$/discord-secrets.env:$PROJECT_DIR$/minecraft-secrets.env:$PROJECT_DIR$/postgres-secrets.env" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONUNBUFFERED" value="1" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||
<option name="IS_MODULE_SDK" value="true" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/start.py" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||
<option name="EMULATE_TERMINAL" value="false" />
|
||||
<option name="MODULE_MODE" value="false" />
|
||||
<option name="REDIRECT_INPUT" value="false" />
|
||||
<option name="INPUT_FILE" value="" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<list>
|
||||
<item itemvalue="Python.MinecraftServerAPI" />
|
||||
<item itemvalue="Python.DB" />
|
||||
<item itemvalue="Python.MinecraftAPI" />
|
||||
<item itemvalue="Python.start" />
|
||||
</list>
|
||||
<recent_temporary>
|
||||
<list>
|
||||
<item itemvalue="Python.start" />
|
||||
<item itemvalue="Python.MinecraftServerAPI" />
|
||||
<item itemvalue="Python.DB" />
|
||||
<item itemvalue="Python.MinecraftAPI" />
|
||||
</list>
|
||||
</recent_temporary>
|
||||
</component>
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="14eb3365-c838-47ac-baf5-458c1d4e585a" name="Changes" comment="" />
|
||||
<created>1758723830290</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1758723830290</updated>
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
<component name="UnknownFeatures">
|
||||
<option featureType="com.intellij.fileTypeFactory" implementationName="*.env" />
|
||||
<option featureType="com.intellij.fileTypeFactory" implementationName="*.config" />
|
||||
</component>
|
||||
<component name="VcsManagerConfiguration">
|
||||
<ignored-roots>
|
||||
<path value="$PROJECT_DIR$" />
|
||||
</ignored-roots>
|
||||
</component>
|
||||
</project>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,96 @@
|
||||
import logging
|
||||
import os.path
|
||||
from typing import List, Dict
|
||||
|
||||
import discord
|
||||
import yaml
|
||||
|
||||
class ConfigLoader:
|
||||
"""
|
||||
Loads the configuration data
|
||||
"""
|
||||
DEFAULT_CONFIG_LOCATION = "mc-auth.config"
|
||||
_CONF_INST = None
|
||||
|
||||
def __init__(self, path=None):
|
||||
ConfigLoader._CONF_INST = self
|
||||
self.log = logging.getLogger("Conf")
|
||||
try:
|
||||
path = os.environ.get('MC_AUTH_CONFIG_LOCATION') if os.environ.get('MC_AUTH_CONFIG_LOCATION') is not None else path
|
||||
except KeyError:
|
||||
pass
|
||||
self.conf_file_path = os.path.join(os.getcwd(), ConfigLoader.DEFAULT_CONFIG_LOCATION) if path is None else path
|
||||
assert(os.path.isfile(self.conf_file_path))
|
||||
|
||||
self.config: dict = dict()
|
||||
self.read_config()
|
||||
|
||||
self.__auth_guilds_per_domain: Dict[str, List[discord.Guild]] = None
|
||||
self.__auth_guilds: List[discord.Guild] = None
|
||||
self.__whitelist_locations_per_domain: Dict[str, str] = None
|
||||
|
||||
async def load_guilds(self, bot):
|
||||
if self.__auth_guilds_per_domain is not None:
|
||||
return
|
||||
self.log.debug("Loading all auth_guild objects from discord API")
|
||||
self.__auth_guilds_per_domain = dict()
|
||||
guilds_bot_is_in: List[discord.Guild] = bot.fetch_guilds()
|
||||
for domain in self.config["minecraft"]["domains"]:
|
||||
gs = []
|
||||
for gid in self.config["minecraft"]["domains"][domain]["auth_guilds"]:
|
||||
async for guild in guilds_bot_is_in:
|
||||
if guild.id == int(gid):
|
||||
gs.append(guild)
|
||||
self.__auth_guilds_per_domain[domain] = gs
|
||||
|
||||
@staticmethod
|
||||
def get_config_loader(path=None):
|
||||
if ConfigLoader._CONF_INST is None:
|
||||
ConfigLoader(path)
|
||||
return ConfigLoader._CONF_INST
|
||||
|
||||
def read_config(self):
|
||||
self.log.debug(f"Reading config \"{self.conf_file_path}\"")
|
||||
self.config = yaml.safe_load(open(self.conf_file_path, 'r'))
|
||||
|
||||
@property
|
||||
def auth_guilds_per_domain(self) -> Dict[str, List[discord.Guild]]:
|
||||
if self.__auth_guilds_per_domain is None:
|
||||
self.log.critical("Looked up ConfigLoader.auth_guilds before it could be loaded by the DiscordBot!")
|
||||
raise ValueError("Looked up ConfigLoader.auth_guilds before it could be loaded by the DiscordBot!")
|
||||
return self.__auth_guilds_per_domain
|
||||
|
||||
@property
|
||||
def auth_guilds(self) -> List[discord.Guild]:
|
||||
if self.__auth_guilds is None:
|
||||
self.__auth_guilds = list()
|
||||
for domain in self.auth_guilds_per_domain:
|
||||
self.__auth_guilds += self.auth_guilds_per_domain[domain]
|
||||
self.__auth_guilds = list(set(self.__auth_guilds))
|
||||
return self.__auth_guilds
|
||||
|
||||
@property
|
||||
def whitelist_location_per_domain(self) -> Dict[str, str]:
|
||||
if self.__whitelist_locations_per_domain is None:
|
||||
self.__whitelist_locations_per_domain = dict()
|
||||
for domain in self.config["minecraft"]["domains"]:
|
||||
self.__whitelist_locations_per_domain[domain] = self.config["minecraft"]["domains"][domain]["whitelist_location"]
|
||||
return self.__whitelist_locations_per_domain
|
||||
|
||||
@property
|
||||
def default_minecraft_domain(self) -> str:
|
||||
return self.config["minecraft"]["default_domain"]
|
||||
|
||||
@property
|
||||
def database_host(self) -> str:
|
||||
return self.config["database"]["host"]
|
||||
|
||||
@property
|
||||
def database_port(self) -> str:
|
||||
return self.config["database"]["port"]
|
||||
|
||||
def post_application_text(self, domain: str) -> str:
|
||||
return self.config["minecraft"]["domains"][domain]["post_application_text"]
|
||||
|
||||
def roles_with_server_access(self, domain: str) -> List[str]:
|
||||
return self.config["minecraft"]["domains"][domain]["roles_with_server_access"]
|
@ -0,0 +1,99 @@
|
||||
import os
|
||||
from typing import List
|
||||
|
||||
import sqlalchemy.engine
|
||||
from sqlalchemy import String, create_engine, URL, Table, Column, ForeignKey
|
||||
from sqlalchemy.orm import DeclarativeBase, Mapped, relationship, Session
|
||||
from sqlalchemy.sql.expression import select
|
||||
from sqlalchemy.testing.schema import mapped_column
|
||||
|
||||
from config.config import ConfigLoader
|
||||
|
||||
def get_engine() -> sqlalchemy.engine.Engine:
|
||||
url_obj = URL.create(
|
||||
"postgresql",
|
||||
username=os.environ.get('POSTGRES_USER'),
|
||||
password=os.environ.get('POSTGRES_PASSWORD'),
|
||||
database=os.environ.get('POSTGRES_DB'),
|
||||
host=ConfigLoader.get_config_loader().database_host,
|
||||
port=ConfigLoader.get_config_loader().database_port
|
||||
)
|
||||
e = create_engine(url_obj)
|
||||
return e
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
pass
|
||||
|
||||
mc_users_on_mc_server = Table(
|
||||
"mc_users_on_mc_server",
|
||||
Base.metadata,
|
||||
Column("minecraft_user", ForeignKey("minecraft_users.uuid"), primary_key=True),
|
||||
Column("minecraft_server", ForeignKey("minecraft_servers.domain"), primary_key=True)
|
||||
)
|
||||
|
||||
class DiscordUser(Base):
|
||||
__tablename__ = "discord_users"
|
||||
|
||||
snowflake_id: Mapped[str] = mapped_column(primary_key=True)
|
||||
handle: Mapped[str] = mapped_column(String(32))
|
||||
|
||||
minecraft_user_uuid = mapped_column(ForeignKey("minecraft_users.uuid"))
|
||||
|
||||
def __repr__(self):
|
||||
return f"User(snowflake_id={self.snowflake_id}, handle={self.handle})"
|
||||
|
||||
class MinecraftUser(Base):
|
||||
__tablename__ = "minecraft_users"
|
||||
|
||||
uuid: Mapped[String] = mapped_column(String(32), primary_key=True)
|
||||
user_name: Mapped[String] = mapped_column(String(16), primary_key=True)
|
||||
|
||||
#discord_user = mapped_column(ForeignKey("discord_users.snowflake_id"))
|
||||
minecraft_servers: Mapped[List["MinecraftServer"]] = relationship(secondary=mc_users_on_mc_server, back_populates="minecraft_users")
|
||||
|
||||
def __repr__(self):
|
||||
return f"MCUser(uuid={self.uuid}, user_name={self.user_name})"
|
||||
|
||||
class MinecraftServer(Base):
|
||||
__tablename__ = "minecraft_servers"
|
||||
|
||||
domain: Mapped[str] = mapped_column(String(64), primary_key=True)
|
||||
minecraft_users: Mapped[List["MinecraftUser"]] = relationship(secondary=mc_users_on_mc_server, back_populates="minecraft_servers")
|
||||
|
||||
def __repr__(self):
|
||||
return f"MinecraftServer(domain={self.domain}, discord_user={self.discord_user})"
|
||||
|
||||
if __name__ == "__main__":
|
||||
url_obj = URL.create(
|
||||
"postgresql",
|
||||
username=os.environ.get('POSTGRES_USER'),
|
||||
password=os.environ.get('POSTGRES_PASSWORD'),
|
||||
database=os.environ.get('POSTGRES_DB'),
|
||||
host=ConfigLoader.get_config_loader().database_host,
|
||||
port=ConfigLoader.get_config_loader().database_port
|
||||
)
|
||||
engine = create_engine(url_obj, echo=True)
|
||||
|
||||
with Session(engine) as session:
|
||||
pass
|
||||
#mc_server = MinecraftServer(domain="feather-mc.pandro.de")
|
||||
#uuid = MinecraftAPI.get_uuid_from_user_name("Pandr0")
|
||||
#mc_user = MinecraftUser(uuid=uuid,
|
||||
# user_name="Pandr0"
|
||||
# )
|
||||
#disc_user = DiscordUser(snowflake_id="682326483921797146",
|
||||
# handle="pandro_",
|
||||
# minecraft_user_uuid=mc_user.uuid)
|
||||
#stmt = update(mc_users_on_mc_server).where().values()
|
||||
#session.execute(stmt)
|
||||
#stmt = insert(mc_users_on_mc_server).values(minecraft_user=uuid, minecraft_server="feather-mc.pandro.de")
|
||||
#session.execute(stmt)
|
||||
#session.commit()
|
||||
stmt = select(MinecraftUser).where(MinecraftUser.user_name == "Pandr0")
|
||||
mc_user = session.execute(stmt).first()[0]
|
||||
stmt = select(MinecraftServer).where(MinecraftServer.minecraft_users.any(MinecraftUser.user_name == "Pandr0")) # TODO figure out how to query minecraft servers with a given minecraft user
|
||||
result = session.execute(stmt).all()
|
||||
|
||||
print(f"PG user: {os.environ.get('POSTGRES_USER')}")
|
||||
#print(f"PG pw: {os.environ.get('POSTGRES_PASSWORD')}")
|
||||
print(f"PG db: {os.environ.get('POSTGRES_DB')}")
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,130 @@
|
||||
import logging
|
||||
|
||||
import discord
|
||||
from discord import Intents, ui, Interaction, Activity, app_commands
|
||||
|
||||
from config.config import ConfigLoader
|
||||
from minecraft_interface import MinecraftAPI, MinecraftServerAPI
|
||||
|
||||
|
||||
class DiscordBot(discord.Client):
|
||||
|
||||
def __init__(self):
|
||||
self.log = logging.getLogger("Discord")
|
||||
self.conf: ConfigLoader = ConfigLoader.get_config_loader()
|
||||
|
||||
try:
|
||||
mc_server_name = self.conf.default_minecraft_domain
|
||||
except KeyError:
|
||||
self.log.critical("Could not find configuration for minecraft server domain in config file!")
|
||||
exit(1)
|
||||
intents = Intents.default()
|
||||
#intents.message_content = True
|
||||
super().__init__(intents=intents, activity=Activity(name= "BotActivity", state=f"Guarding {mc_server_name}", type=discord.ActivityType.custom))
|
||||
|
||||
self.tree = app_commands.CommandTree(self)
|
||||
|
||||
|
||||
def start_discord_bot(self, token: str, log_level):
|
||||
logging.getLogger("Main").info("Starting the discord bot.")
|
||||
self.run(token, log_level=logging.WARN) # blocks
|
||||
|
||||
async def setup_hook(self) -> None:
|
||||
# Sync the application command with all Authentication Guilds
|
||||
await self.register_commands()
|
||||
for guild in self.conf.auth_guilds:
|
||||
await self.tree.sync(guild=guild)
|
||||
|
||||
async def register_commands(self):
|
||||
await self.conf.load_guilds(self)
|
||||
self.tree.command(
|
||||
name="signup_for_minecraft_server",
|
||||
description="Enter your minecraft account name to sign up for the minecraft server",
|
||||
guilds=self.conf.auth_guilds
|
||||
)(self.signup_for_minecraft)
|
||||
self.tree.command(
|
||||
name="leave_minecraft_server",
|
||||
description="Removes you from the minecraft server.",
|
||||
guilds=self.conf.auth_guilds
|
||||
)(self.leave_minecraft)
|
||||
|
||||
async def on_ready(self):
|
||||
await self.conf.load_guilds(self)
|
||||
self.log.info(f"We have logged in as {self.user}")
|
||||
|
||||
async def signup_for_minecraft(self, interaction: Interaction):
|
||||
if interaction.guild in self.conf.auth_guilds:
|
||||
|
||||
user_has_correct_role = False
|
||||
for user_role in interaction.user.roles:
|
||||
if str(user_role.id) in self.conf.roles_with_server_access(self.conf.default_minecraft_domain):
|
||||
user_has_correct_role = True
|
||||
if not user_has_correct_role:
|
||||
await interaction.response.send_message("Access denied!\n"
|
||||
"You do not have the correct role to use this command.",
|
||||
ephemeral=True)
|
||||
return
|
||||
self.log.info(
|
||||
f"Command \"signup_for_minecraft\" was called from guild \"{interaction.guild}\" ({interaction.guild.id}) "
|
||||
f"by \"{interaction.user.name}\" ({interaction.user.id})")
|
||||
await interaction.response.send_modal(MinecraftModal(interaction))
|
||||
else:
|
||||
self.log.warning(f"Command \"signup_for_minecraft\" was called from outside an auth_guild! "
|
||||
f"{interaction.guild} by {interaction.user}")
|
||||
|
||||
async def leave_minecraft(self, interaction: Interaction):
|
||||
if interaction.guild in self.conf.auth_guilds:
|
||||
user_has_correct_role = False
|
||||
for user_role in interaction.user.roles:
|
||||
if str(user_role.id) in self.conf.roles_with_server_access(self.conf.default_minecraft_domain):
|
||||
user_has_correct_role = True
|
||||
if not user_has_correct_role:
|
||||
await interaction.response.send_message("Access denied!\n"
|
||||
"You do not have the correct role to use this command.",
|
||||
ephemeral=True)
|
||||
return
|
||||
self.log.info(
|
||||
f"Command \"leave_minecraft_server\" was called from guild \"{interaction.guild}\" ({interaction.guild.id}) "
|
||||
f"by \"{interaction.user.name}\" ({interaction.user.id})"
|
||||
)
|
||||
await MinecraftServerAPI.handle_minecraft_retirement(self.conf.default_minecraft_domain, interaction.user, interaction)
|
||||
|
||||
class MinecraftModal(ui.Modal, title="Minecraft server application"):
|
||||
mc_name = ui.TextInput(label='Minecraft Username')
|
||||
#reason = ui.TextInput(label='Why do you want to join?', style=discord.TextStyle.paragraph)
|
||||
|
||||
def __init__(self, interaction: discord.Interaction):
|
||||
super().__init__()
|
||||
self.log = logging.getLogger("Discord")
|
||||
|
||||
async def on_submit(self, interaction: Interaction) -> None:
|
||||
config: ConfigLoader = ConfigLoader.get_config_loader()
|
||||
|
||||
if interaction.guild not in config.auth_guilds:
|
||||
self.log.warning(f"Interaction from non-auth_guilds: {interaction} {interaction.guild} {interaction.user}")
|
||||
await interaction.response.send_message(f"Interaction came from a guild not on the list. Not doing anything (except logging this)!")
|
||||
return
|
||||
|
||||
# validating given minecraft username
|
||||
mc_uuid = MinecraftAPI.get_uuid_from_user_name(str(self.mc_name))
|
||||
if mc_uuid is not None:
|
||||
self.log.info(f"Received server application with valid minecraft username \"{self.mc_name}\" from "
|
||||
f"{interaction.user.name} ({interaction.user.id}) on "
|
||||
f"{interaction.guild.name} ({interaction.guild.id})!")
|
||||
else:
|
||||
self.log.warning(f"Received server application with INVALID minecraft username \"{self.mc_name}\" from "
|
||||
f"{interaction.user.name} ({interaction.user.id}) on "
|
||||
f"{interaction.guild.name} ({interaction.guild.id})!")
|
||||
await interaction.response.send_message(f'The minecraft username \"{self.mc_name}\" failed verification!\n\n'
|
||||
f'Aborted!',
|
||||
ephemeral=True)
|
||||
return
|
||||
|
||||
# Got a valid application.
|
||||
await MinecraftServerAPI.handle_minecraft_application(str(self.mc_name), mc_uuid, config.default_minecraft_domain,
|
||||
interaction.user, interaction)
|
||||
await interaction.response.send_message("Application received!\n"
|
||||
"Give it a minute. If it hasn't worked by then please "
|
||||
"contact an admin instead!\n\n"
|
||||
"Remember that you will be responsible for any actions of that minecraft account!",
|
||||
ephemeral=True)
|
@ -0,0 +1,25 @@
|
||||
import logging
|
||||
|
||||
|
||||
class Log:
|
||||
|
||||
@staticmethod
|
||||
def setup(level="DEBUG"):
|
||||
level = getattr(logging, level)
|
||||
mlog = logging.getLogger("Main")
|
||||
dlog = logging.getLogger("Discord")
|
||||
mclog = logging.getLogger("MC-API")
|
||||
console_handler = logging.StreamHandler()
|
||||
formatter = logging.Formatter(
|
||||
fmt="[%(asctime)s][%(levelname)s] %(name)s: %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S"
|
||||
)
|
||||
console_handler.setFormatter(formatter)
|
||||
|
||||
logs = [mlog, dlog, mclog]
|
||||
for log in logs:
|
||||
log.handlers.clear()
|
||||
log.setLevel(level)
|
||||
log.addHandler(console_handler)
|
||||
dlog.addHandler(console_handler)
|
||||
mlog.debug("Logging has been setup!")
|
Binary file not shown.
Binary file not shown.
@ -0,0 +1,13 @@
|
||||
minecraft:
|
||||
default_domain: "your-mc-server-domain"
|
||||
domains:
|
||||
your-mc-server-domain:
|
||||
auth_guilds:
|
||||
- "<SNOWFLAKE ID OF YOUR DISCORD GUILD>"
|
||||
roles_with_server_access:
|
||||
- "<ROLE ID OF ROLE THAT GETS ACCESS>"
|
||||
whitelist_location: "./whitelist.json"
|
||||
post_application_text: "<NOT IMPLEMENTED YET>"
|
||||
database:
|
||||
host: "127.0.0.1"
|
||||
port: 5432
|
@ -0,0 +1,63 @@
|
||||
import logging
|
||||
|
||||
import requests
|
||||
|
||||
from log.Log import Log
|
||||
from version import VERSION
|
||||
|
||||
HEADERS = {'User-agent': f"DiscordMinecraft-Authenticator ({VERSION})(peery.coding@gmail.com)"}
|
||||
|
||||
def is_valid_user_name(user_name: str) -> bool:
|
||||
"""
|
||||
Checks if user_name is a valid minecraft player account by querying api.mojang.com if the profile exists
|
||||
:param user_name:
|
||||
:return:
|
||||
"""
|
||||
log = logging.getLogger("McAPI")
|
||||
url = f"https://api.mojang.com/users/profiles/minecraft/{user_name}"
|
||||
response = requests.get(url, headers=HEADERS)
|
||||
|
||||
if response.status_code == 404:
|
||||
log.info(f"Looked up user name \"{user_name}\". Failed!")
|
||||
return False
|
||||
elif response.status_code == 200:
|
||||
try:
|
||||
uuid = response.json()['id']
|
||||
except KeyError:
|
||||
log.critical(f"Unexpected response from {url}! Response ({response.status_code}): {response.json()}")
|
||||
return False
|
||||
log.info(f"Looked up user name \"{user_name}\" (UUID: \"{uuid}\"). Success!")
|
||||
return True
|
||||
else:
|
||||
log.error(f"Got unexpected status code from user_name lookup \"{user_name}\": {response.status_code}")
|
||||
return False
|
||||
|
||||
def get_uuid_from_user_name(user_name: str) -> str:
|
||||
"""
|
||||
Checks if user_name is a valid minecraft player account by querying api.mojang.com if the profile exists
|
||||
:param user_name:
|
||||
:return:
|
||||
"""
|
||||
log = logging.getLogger("McAPI")
|
||||
url = f"https://api.mojang.com/users/profiles/minecraft/{user_name}"
|
||||
response = requests.get(url, headers=HEADERS)
|
||||
|
||||
if response.status_code == 404:
|
||||
log.info(f"Looked up user name \"{user_name}\". Failed!")
|
||||
return None
|
||||
elif response.status_code == 200:
|
||||
try:
|
||||
uuid = response.json()['id']
|
||||
log.info(f"Looked up user name \"{user_name}\" (UUID: \"{uuid}\"). Success!")
|
||||
return uuid
|
||||
except KeyError:
|
||||
log.critical(f"Unexpected response from {url}! Response ({response.status_code}): {response.json()}")
|
||||
return None
|
||||
else:
|
||||
log.error(f"Got unexpected status code from user_name lookup \"{user_name}\": {response.status_code}")
|
||||
return None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
Log.setup("DEBUG")
|
||||
print(is_valid_user_name("MausKomant"))
|
@ -0,0 +1,224 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from time import sleep
|
||||
from typing import List
|
||||
|
||||
import discord
|
||||
from mcrcon import MCRcon
|
||||
from sqlalchemy import select, and_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from config.config import ConfigLoader
|
||||
from database.DB import get_engine, MinecraftUser, MinecraftServer, DiscordUser
|
||||
from log.Log import Log
|
||||
|
||||
|
||||
def add_user_name_to_whitelist(domain: str, user_name: str):
|
||||
"""
|
||||
Adds a minecraft user_name to the whitelist
|
||||
:param domain:
|
||||
:param user_name:
|
||||
:return:
|
||||
"""
|
||||
log = logging.getLogger("MC-API")
|
||||
with MCRcon(os.environ.get('MCRCON_HOST'), os.environ.get('MCRCON_PW')) as mcr:
|
||||
log.info(f"Adding minecraft user \"{user_name}\" to the whitelist of \"{domain}\"")
|
||||
resp = mcr.command(f"/whitelist add {user_name}")
|
||||
print(resp)
|
||||
|
||||
def get_all_users_on_whitelist(domain: str) -> dict:
|
||||
"""
|
||||
Gets a list of all users on the whitelist by reading the server's whitelist.json
|
||||
:param domain:
|
||||
:return:
|
||||
"""
|
||||
c: ConfigLoader = ConfigLoader.get_config_loader()
|
||||
with open(c.whitelist_location_per_domain[domain], 'r') as whitelist_file:
|
||||
whitelist = json.load(whitelist_file)
|
||||
return whitelist
|
||||
|
||||
def get_list_of_users_on_whitelist(domain: str) -> List[str]:
|
||||
on_whitelist = [name for name in
|
||||
map(lambda x: x['name'], get_all_users_on_whitelist(domain))]
|
||||
return on_whitelist
|
||||
|
||||
def remove_user_name_from_whitelist(domain: str, user_name: str):
|
||||
"""
|
||||
Removes a minecraft user_name from the whitelist
|
||||
:param domain:
|
||||
:param user_name:
|
||||
:return:
|
||||
"""
|
||||
log = logging.getLogger("MC-API")
|
||||
with MCRcon(os.environ.get('MCRCON_HOST'), os.environ.get('MCRCON_PW')) as mcr:
|
||||
log.info(f"Removing minecraft user \"{user_name}\" from the whitelist of \"{domain}\"")
|
||||
resp = mcr.command(f"/whitelist remove {user_name}")
|
||||
print(resp)
|
||||
|
||||
async def handle_minecraft_application(mc_name: str, mc_uuid: str, domain: str, discord_user: discord.User,
|
||||
interaction: discord.Interaction):
|
||||
"""
|
||||
Applies an application to the server whitelist or ignores it depending on if the minecraft user is already whitelisted.
|
||||
|
||||
Will alter the database to reflecte the current state.
|
||||
:param mc_name:
|
||||
:param mc_uuid:
|
||||
:param domain:
|
||||
:param discord_user:
|
||||
:param interaction
|
||||
:return:
|
||||
"""
|
||||
log = logging.getLogger("MC-API")
|
||||
c: ConfigLoader = ConfigLoader.get_config_loader()
|
||||
|
||||
engine = get_engine()
|
||||
with Session(engine) as session:
|
||||
# query if the user is already related to our server
|
||||
stmt = select(MinecraftUser).where(
|
||||
MinecraftUser.minecraft_servers.any(and_(MinecraftServer.domain == domain, MinecraftUser.user_name == mc_name)))
|
||||
mc_user_on_server: MinecraftUser = session.scalars(stmt).first()
|
||||
# query for the server obj
|
||||
stmt = select(MinecraftServer).where(MinecraftServer.domain == domain)
|
||||
dc_user: DiscordUser = session.scalars(
|
||||
select(DiscordUser).where(DiscordUser.snowflake_id == str(discord_user.id))
|
||||
).first()
|
||||
curr_server: MinecraftServer = session.scalars(stmt).first()
|
||||
|
||||
on_whitelist = get_list_of_users_on_whitelist(domain)
|
||||
|
||||
if mc_user_on_server is not None: # this user is already associated with the server (could be missing on whitelist?)
|
||||
log.info(
|
||||
f"Found minecraft username \"{mc_name}\" to be already associated with the server. Aborting ...")
|
||||
if mc_name not in on_whitelist:
|
||||
log.error(f"Minecraft username \"{mc_name}\" was found in the database but is "
|
||||
f"not in \"whitelist.json\"! Did someone manually remove them? Removing the relationship ...")
|
||||
mc_user_on_server.minecraft_servers.remove(curr_server)
|
||||
session.add(mc_user_on_server)
|
||||
session.commit()
|
||||
if dc_user.minecraft_user_uuid == mc_uuid:
|
||||
log.info(f"Discord user \"{dc_user.handle}\" ({dc_user.snowflake_id}) tried to add minecraft user "
|
||||
f"\"{mc_name}\" ({mc_uuid}) twice! Aborting...")
|
||||
#interaction.response.send_message(f"The minecraft user \"{mc_name}\" was already added by you!\n"
|
||||
# f"Not doing anything because this seems unnecessary.\n\n"
|
||||
# f"If this is an error inform an admin!",
|
||||
# ephemeral=True)
|
||||
return # do not inform user about this, otherwise leaks who is on the server by guessing their name with the bot
|
||||
# minecraft user is not associated with server or was just dis-associated (could still be on whitelist)
|
||||
log.info(
|
||||
f"Did not find minecraft user \"{mc_name}\" in relation to server \"{curr_server.domain}\"")
|
||||
if mc_name in on_whitelist:
|
||||
log.warning(
|
||||
f"Minecraft username \"{mc_name}\" was not found in the database but is "
|
||||
f"in \"whitelist.json\"! Were they added manually? Ignoring that.")
|
||||
return
|
||||
|
||||
# query for the minecraft user
|
||||
mc_user = session.scalars(
|
||||
select(MinecraftUser).where(MinecraftUser.user_name == str(mc_name))
|
||||
).first()
|
||||
if mc_user is None: # create mc_user
|
||||
mc_user = MinecraftUser(user_name=str(mc_name), uuid=mc_uuid)
|
||||
|
||||
session.add(mc_user)
|
||||
session.commit()
|
||||
|
||||
if dc_user is None: # got to create discord user
|
||||
dc_user = DiscordUser(snowflake_id=str(discord_user.id),
|
||||
handle=str(discord_user.name),
|
||||
minecraft_user_uuid=mc_uuid)
|
||||
session.add(dc_user)
|
||||
else: # discord user is known
|
||||
if dc_user.minecraft_user_uuid is not None and dc_user.minecraft_user_uuid != mc_uuid: # discord user already had another minecraft account
|
||||
old_mc_user: MinecraftUser = session.scalars(select(MinecraftUser).where(
|
||||
MinecraftUser.uuid == dc_user.minecraft_user_uuid
|
||||
)).first()
|
||||
log.info(f"Discord user \"{dc_user.handle}\" ({dc_user.snowflake_id}) already had a minecraft uuid "
|
||||
f"associated: \"{old_mc_user.user_name}\" ({old_mc_user.uuid})\n"
|
||||
f"Updating it to \"{mc_name}\" ({mc_uuid})!")
|
||||
remove_user_name_from_whitelist(domain, str(old_mc_user.user_name))
|
||||
dc_user.minecraft_user_uuid = mc_uuid
|
||||
session.add(dc_user)
|
||||
mc_user.minecraft_servers.append(curr_server)
|
||||
session.commit()
|
||||
|
||||
add_user_name_to_whitelist(domain, mc_name)
|
||||
#await interaction.response.send_message("Thanks for your response. You've been added to the server's whitelist!\n"
|
||||
# "Enjoy your time playing!\n\n"
|
||||
# "Also don't forget to inform yourself about available mods or "
|
||||
# "which minecraft version to use! Some are: \n"
|
||||
# f"{c.post_application_text(domain)}",
|
||||
# ephemeral=True)
|
||||
|
||||
async def handle_minecraft_retirement(domain: str, discord_user: discord.User, interaction: discord.Interaction):
|
||||
"""
|
||||
Only removes any minecraft user associated with a discord user.
|
||||
:param domain:
|
||||
:param discord_user:
|
||||
:param interaction:
|
||||
:return:
|
||||
"""
|
||||
log = logging.getLogger("MC-API")
|
||||
|
||||
engine = get_engine()
|
||||
with Session(engine) as session:
|
||||
dc_user: DiscordUser = session.scalars(select(DiscordUser).where(
|
||||
DiscordUser.snowflake_id == str(discord_user.id)
|
||||
)).first()
|
||||
if dc_user is None: # unknown discord user called this command
|
||||
log.info(f"Can't find discord user \"{discord_user.name}\" (\"{discord_user.id}\") in the database!")
|
||||
await interaction.response.send_message(f"Hey, {discord_user.name}!\nWould love to help but don't think we have met before!\n"
|
||||
f"If you're still on the whitelist of \"{domain}\" "
|
||||
"please tell an admin or moderator instead. "
|
||||
"They'll figure this out for you!",
|
||||
ephemeral=True)
|
||||
return
|
||||
else: # discord user has interacted with this bot before
|
||||
if dc_user.minecraft_user_uuid is None: # no minecraft user known (used this command before or manual edit)?
|
||||
log.info(f"Found discord user \"{dc_user.handle}\" ({dc_user.snowflake_id}) in the database "
|
||||
f"but there was no minecraft UUID associated with it.")
|
||||
await interaction.response.send_message("Couldn't find any minecraft user associated with your account.\n"
|
||||
"But I've seen you before! Have you used this command already?",
|
||||
ephemeral=True)
|
||||
return
|
||||
|
||||
mc_user: MinecraftUser = session.scalars(select(MinecraftUser).where(
|
||||
MinecraftUser.uuid == dc_user.minecraft_user_uuid
|
||||
)).first()
|
||||
if mc_user is None:
|
||||
log.critical(f"Could not find minecraft user with UUID \"{dc_user.minecraft_user_uuid}\" but it was "
|
||||
f"associated with discord account \"{dc_user.handle}\" ({dc_user.snowflake_id}). "
|
||||
f"Something is broken in the database schema! Did a delete not cascade?")
|
||||
exit(1)
|
||||
curr_server: MinecraftServer = session.scalars(select(MinecraftServer).where(
|
||||
MinecraftServer.domain == domain
|
||||
)).first()
|
||||
if curr_server is None: # domain is unknown to the database?!
|
||||
log.critical(f"Tried to look up minecraft server \"{domain}\" but the database query was empty! \n"
|
||||
f"Something is broken in the database!")
|
||||
exit(1)
|
||||
|
||||
dc_user.minecraft_user_uuid = None
|
||||
session.add(dc_user)
|
||||
mc_user.minecraft_servers.remove(curr_server)
|
||||
session.add(mc_user)
|
||||
log.info(f"Discord user \"{dc_user.handle}\" ({dc_user.snowflake_id}) is leaving server \"{domain}\" with "
|
||||
f"minecraft user \"{mc_user.user_name}\" ({mc_user.uuid})!")
|
||||
remove_user_name_from_whitelist(curr_server.domain, str(mc_user.user_name))
|
||||
session.commit()
|
||||
await interaction.response.send_message(f"Removed your minecraft account \"{mc_user.user_name}\" "
|
||||
f"from the server's whitelist.\n"
|
||||
f"Take care!",
|
||||
ephemeral=True)
|
||||
|
||||
if __name__ == "__main__":
|
||||
Log.setup()
|
||||
|
||||
print(get_all_users_on_whitelist("feather-mc.pandro.de"))
|
||||
sleep(2)
|
||||
add_user_name_to_whitelist("feather-mc.pandro.de", "MausKomant")
|
||||
sleep(2)
|
||||
print(get_all_users_on_whitelist("feather-mc.pandro.de"))
|
||||
remove_user_name_from_whitelist("feather-mc.pandro.de", "MausKomant")
|
||||
sleep(2)
|
||||
print(get_all_users_on_whitelist("feather-mc.pandro.de"))
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,6 @@
|
||||
discord>=2.3.2
|
||||
SQLAlchemy>=2.0.43
|
||||
pyyaml>=6.0.3
|
||||
requests>=2.32.5
|
||||
psycopg2-binary>=2.9.10
|
||||
mcrcon>=0.7.0
|
@ -0,0 +1,27 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
from database.DB import get_engine
|
||||
from discord_bot.discord_bot import DiscordBot
|
||||
from log.Log import Log
|
||||
|
||||
# TODO use docker secrets instead of env variables
|
||||
# TODO rework MINECRAFT_SERVER_DOMAIN env
|
||||
|
||||
# TODO create command /lookup_minecraft_name to see which discord account is associated with a minecraft name
|
||||
# TODO make /lookup_minecraft_name only available to a configured role #2
|
||||
|
||||
def main():
|
||||
Log.setup(os.environ.get('LOG'))
|
||||
if not (len(os.environ.get('DISCORD_BOT_TOKEN')) > 0):
|
||||
logging.getLogger("Main").critical("No discord bot token was found! Please generate a token at "
|
||||
"https://discord.com/developers/applications/<your application id>/bot "
|
||||
"and make sure it is present as the "
|
||||
"environment variable \"DISCORD_BOT_TOKEN\".")
|
||||
exit(1)
|
||||
get_engine()
|
||||
disc = DiscordBot()
|
||||
disc.start_discord_bot(token=os.environ.get('DISCORD_BOT_TOKEN'), log_level=os.environ.get('LOG'))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -0,0 +1 @@
|
||||
VERSION = "v0.1"
|
@ -0,0 +1 @@
|
||||
test-env/data/mc-server/whitelist.json
|
@ -0,0 +1,59 @@
|
||||
services:
|
||||
db:
|
||||
image: postgres:17-alpine
|
||||
restart: unless-stopped
|
||||
# ports:
|
||||
# - 127.0.0.1:5432:5432 # only when testing from the outside
|
||||
healthcheck:
|
||||
test: ['CMD', 'pg_isready', '-U', "mc-authenticator", "minecraft_access"]
|
||||
timeout: 5s
|
||||
retries: 2
|
||||
env_file:
|
||||
- ./env/postgres-secrets.env
|
||||
volumes:
|
||||
- "./data/postgres_data:/var/lib/postgresql/data"
|
||||
networks:
|
||||
- internal
|
||||
|
||||
paper:
|
||||
image: "docker.io/eclipse-temurin:21-jre"
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:25565:25565/tcp" # default mc
|
||||
- "127.0.0.1:25565:25565/udp" # default mc
|
||||
- "127.0.0.1:24454:24454/udp" # simple-voice-chat plugin
|
||||
# - "127.0.0.1:25575:25575/tcp" # RCON, only when testing from the outside
|
||||
volumes:
|
||||
- ./data/mc-server:/app
|
||||
working_dir: /app
|
||||
command: "java -Xms8192M -Xmx8192M -jar paper.jar nogui"
|
||||
stdin_open: true
|
||||
tty: true
|
||||
networks:
|
||||
- internet
|
||||
mc-authenticator:
|
||||
image: "minecraft-authenticator:latest"
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- ./env/postgres-secrets.env
|
||||
- ./env/discord-secrets.env
|
||||
- ./env/minecraft-secrets.env
|
||||
- ./env/base.env
|
||||
volumes:
|
||||
- ./data/mc-server/whitelist.json:/home/python_user/app/whitelist.json:ro
|
||||
- ./config/mc-authenticator/mc-auth.config:/home/python_user/app/mc-auth.config:ro
|
||||
depends_on:
|
||||
- db
|
||||
- paper
|
||||
networks:
|
||||
- internal
|
||||
- internet
|
||||
|
||||
networks:
|
||||
internet:
|
||||
enable_ipv6: true
|
||||
ipam:
|
||||
config:
|
||||
- subnet: fdf7:53f5:341e::/64
|
||||
internal:
|
||||
internal: true
|
@ -0,0 +1,26 @@
|
||||
-- Creating the DB structure
|
||||
-- Version 1.0 (2025-09-25)
|
||||
|
||||
CREATE TABLE IF NOT EXISTS minecraft_users (
|
||||
uuid VARCHAR(32) PRIMARY KEY not null,
|
||||
user_name VARCHAR(16) not null
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS discord_users (
|
||||
snowflake_id VARCHAR(32) PRIMARY KEY not null,
|
||||
handle VARCHAR(32) not null,
|
||||
minecraft_user_uuid VARCHAR(32),
|
||||
FOREIGN KEY (minecraft_user_uuid) REFERENCES minecraft_users(uuid) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS minecraft_servers (
|
||||
domain VARCHAR(128) PRIMARY KEY not null
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS mc_users_on_mc_server (
|
||||
minecraft_user VARCHAR(32) not null,
|
||||
minecraft_server VARCHAR(64) not null,
|
||||
PRIMARY KEY(minecraft_user, minecraft_server),
|
||||
FOREIGN KEY(minecraft_user) REFERENCES minecraft_users(uuid) ON DELETE CASCADE,
|
||||
FOREIGN KEY(minecraft_server) REFERENCES minecraft_servers(domain) ON DELETE CASCADE
|
||||
);
|
Loading…
Reference in New Issue